farming-code 2.2.29 → 2.2.32
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 +25 -66
- package/README.zh_cn.md +15 -66
- package/THIRD_PARTY_NOTICES.md +7 -0
- package/backend/acp/{client-services.js → client-services.cjs} +132 -134
- package/backend/acp/packaged-claude-acp.cjs +37 -0
- package/backend/acp/packaged-codex-acp.cjs +37 -0
- package/backend/acp/patch-decisions.cjs +182 -0
- package/backend/acp/permission-security.cjs +131 -0
- package/backend/{acp-checkpoint-store.js → acp-checkpoint-store.cjs} +90 -59
- package/backend/{acp-runtime.js → acp-runtime.cjs} +1219 -1125
- package/backend/acp-session-state.cjs +622 -0
- package/backend/acp-transcript.cjs +525 -0
- package/backend/agent-env.cjs +257 -0
- package/backend/agent-extension-discovery.cjs +452 -0
- package/backend/agent-extension-inventory.cjs +116 -0
- package/backend/agent-json-stream.cjs +283 -0
- package/backend/agent-lifecycle-journal.cjs +185 -0
- package/backend/{agent-manager.js → agent-manager.cjs} +3116 -4275
- package/backend/agent-order-transaction.cjs +85 -0
- package/backend/agent-order.cjs +159 -0
- package/backend/agent-provider-session.cjs +98 -0
- package/backend/agent-runtime-binding.cjs +179 -0
- package/backend/agent-session-history.cjs +1022 -0
- package/backend/agent-session-inventory.cjs +291 -0
- package/backend/async-cache.cjs +104 -0
- package/backend/atomic-json-store.cjs +67 -0
- package/backend/auth.cjs +261 -0
- package/backend/authoritative-inventory-cache.cjs +383 -0
- package/backend/business-health.cjs +68 -0
- package/backend/chat-runtime.cjs +50 -0
- package/backend/claude-settings.cjs +132 -0
- package/backend/cli-agents.cjs +435 -0
- package/backend/codex-context-window.cjs +196 -0
- package/backend/codex-models.cjs +228 -0
- package/backend/codex-session-archive.cjs +82 -0
- package/backend/codex-session-history.cjs +590 -0
- package/backend/{codex-terminal-profile.js → codex-terminal-profile.cjs} +149 -155
- package/backend/codex-transcript-sanitizer.cjs +130 -0
- package/backend/codex-transcript.cjs +2107 -0
- package/backend/command-runner-child.cjs +105 -0
- package/backend/config-manager.cjs +1263 -0
- package/backend/control-api.cjs +439 -0
- package/backend/data/LICENSE.cua +21 -0
- package/backend/executable-discovery.cjs +204 -0
- package/backend/farming-agent-bootstrap.cjs +81 -0
- package/backend/farming-agent-bootstrap.zh_cn.md +6 -2
- package/backend/{farming-app-cli.js → farming-app-cli.cjs} +441 -493
- package/backend/farming-cli.cjs +414 -0
- package/backend/farming-net-pass.cjs +319 -0
- package/backend/farming-net-registry.cjs +123 -0
- package/backend/farming-net-server.cjs +309 -0
- package/backend/farming-session-store.cjs +651 -0
- package/backend/git-worktree-info.cjs +181 -0
- package/backend/haiku-token.cjs +828 -0
- package/backend/index-html.cjs +96 -0
- package/backend/input-parts.cjs +68 -0
- package/backend/input-routing.cjs +40 -0
- package/backend/{local-session-engine.js → local-session-engine.cjs} +276 -282
- package/backend/main-agent-skills.cjs +309 -0
- package/backend/main-page-session.cjs +132 -0
- package/backend/native-pty-controller-generation.cjs +139 -0
- package/backend/{native-pty-host-client.js → native-pty-host-client.cjs} +338 -346
- package/backend/native-pty-host-identity.cjs +125 -0
- package/backend/native-pty-host-path.cjs +68 -0
- package/backend/{native-pty-host.js → native-pty-host.cjs} +480 -569
- package/backend/native-session-engine.cjs +296 -0
- package/backend/network.cjs +57 -0
- package/backend/npm-update-helper.cjs +399 -0
- package/backend/packaged-node-pty.cjs +120 -0
- package/backend/preview-session-manager.cjs +89 -0
- package/backend/provider-adapters.cjs +492 -0
- package/backend/provider-home-configuration.cjs +211 -0
- package/backend/provider-session-id.cjs +79 -0
- package/backend/provider-session-service.cjs +333 -0
- package/backend/qr-share-tickets.cjs +119 -0
- package/backend/resource-broadcast-protocol.cjs +50 -0
- package/backend/review-diff-router.cjs +202 -0
- package/backend/review-diff-service.cjs +1267 -0
- package/backend/review-session-router.cjs +93 -0
- package/backend/review-session-service.cjs +470 -0
- package/backend/review-session-store.cjs +161 -0
- package/backend/review-state-router.cjs +132 -0
- package/backend/review-state-store.cjs +299 -0
- package/backend/run-history-store.cjs +97 -0
- package/backend/runtime-dependency-manager.cjs +893 -0
- package/backend/runtime-executable-invocation.cjs +37 -0
- package/backend/runtime-observation.cjs +96 -0
- package/backend/server-process-identity.cjs +70 -0
- package/backend/server.cjs +3356 -0
- package/backend/session-engine-bridge.cjs +144 -0
- package/backend/session-engine-router.cjs +105 -0
- package/backend/session-engine.cjs +64 -0
- package/backend/session-stream-protocol.cjs +181 -0
- package/backend/shell-busy-integration.cjs +442 -0
- package/backend/slash-command-discovery.cjs +423 -0
- package/backend/storage-layout.cjs +263 -0
- package/backend/system-monitor.cjs +96 -0
- package/backend/terminal-attach-checkpoint.cjs +73 -0
- package/backend/terminal-exit-quiescence.cjs +57 -0
- package/backend/{terminal-reducer-flow-control.js → terminal-reducer-flow-control.cjs} +45 -22
- package/backend/terminal-runtime-cleanup.cjs +99 -0
- package/backend/{terminal-screen-state.js → terminal-screen-state.cjs} +100 -108
- package/backend/{terminal-screen-worker-pool.js → terminal-screen-worker-pool.cjs} +89 -79
- package/backend/{terminal-screen-worker-thread.js → terminal-screen-worker-thread.cjs} +64 -65
- package/backend/terminal-screen-worker.cjs +352 -0
- package/backend/terminal-state-serialization.cjs +147 -0
- package/backend/{terminal-status.js → terminal-status.cjs} +137 -187
- package/backend/theme-manager.cjs +185 -0
- package/backend/update-service.cjs +675 -0
- package/backend/usage-forecast.cjs +95 -0
- package/backend/{usage-history-client.js → usage-history-client.cjs} +144 -94
- package/backend/usage-history-smoke.cjs +85 -0
- package/backend/usage-history-worker.cjs +60 -0
- package/backend/{usage-monitor.js → usage-monitor.cjs} +363 -390
- package/backend/workspace-directory.cjs +239 -0
- package/backend/workspace-discovery.cjs +271 -0
- package/backend/{workspace-file-router.js → workspace-file-router.cjs} +213 -282
- package/backend/{workspace-file-service.js → workspace-file-service.cjs} +1139 -1318
- package/backend/workspace-root-registry.cjs +192 -0
- package/bin/farming +1 -1
- package/config/farming.deploy.env.example +1 -1
- package/config/farming.install.env.example +1 -1
- package/dist/acp/codex-acp-1.1.4.mjs +405 -8
- package/dist/assets/App-BCkdpWB6.css +1 -0
- package/dist/assets/App-CRjKpaGp.js +697 -0
- package/dist/assets/CodeSelect-Ci8sKZPU.js +1 -0
- package/dist/assets/CodeSelect-T2eNzrOc.css +1 -0
- package/dist/assets/{FileEditorMarkdownPreview-Cs9cLv8a.js → FileEditorMarkdownPreview-V06QHlAg.js} +55 -55
- package/dist/assets/FileEditorPane-BX-Wjg9U.js +2 -0
- package/dist/assets/IconGlyphs-YhV7tqd4.js +1 -0
- package/dist/assets/ProjectFilesSection-BDT2_r0o.js +12 -0
- package/dist/assets/ReviewPage-BqAs5sG1.js +3 -0
- package/dist/assets/code-dark-B7eM_9G9.css +1 -0
- package/dist/assets/{cssMode-Bdg5RpcJ.js → cssMode-ADdAlxTO.js} +1 -1
- package/dist/assets/{editor.api2-D74ufkbr.js → editor.api2-BFLAr0YF.js} +162 -162
- package/dist/assets/file-icons-BpeqWkIB.js +1 -0
- package/dist/assets/{freemarker2-DfMDJfnE.js → freemarker2-C90q5jrp.js} +1 -1
- package/dist/assets/{handlebars-BEdPtzMT.js → handlebars-BwUQcEDq.js} +1 -1
- package/dist/assets/{html-B1fPb1QD.js → html-1s3uPKfh.js} +1 -1
- package/dist/assets/{htmlMode-CY2ZjHxH.js → htmlMode-xtKD99de.js} +1 -1
- package/dist/assets/{index-CQNaZc3l.js → index-C8MchCem.js} +3 -3
- package/dist/assets/{javascript-BV2OsWA0.js → javascript-Ph7ijyny.js} +1 -1
- package/dist/assets/{jsonMode-zd07aeuX.js → jsonMode-D-jGAnFH.js} +1 -1
- package/dist/assets/linkComputer-CbGyI_qx.js +1 -0
- package/dist/assets/{liquid-BParI24X.js → liquid-0IamE_cI.js} +1 -1
- package/dist/assets/{lspLanguageFeatures-BzredkoV.js → lspLanguageFeatures-BxnQunWF.js} +1 -1
- package/dist/assets/main-CLKXa6hf.css +1 -0
- package/dist/assets/{mdx-vGzUJN98.js → mdx-DdNmfivn.js} +1 -1
- package/dist/assets/{monaco.contribution-BmaeBQEc.js → monaco.contribution-B43jsUs1.js} +2 -2
- package/dist/assets/{python-CUwdLgsI.js → python-IAt4X5TS.js} +1 -1
- package/dist/assets/{razor-BScebUxU.js → razor-DRkFYW0S.js} +1 -1
- package/dist/assets/review-C8-oPKXk.css +1 -0
- package/dist/assets/{tsMode-DzUAibXK.js → tsMode-_d_4U2gf.js} +1 -1
- package/dist/assets/{typescript-D9EOeM7N.js → typescript-C7zcMq9l.js} +1 -1
- package/dist/assets/{workers-DAiomYZM.js → workers-D0nOyJzf.js} +1 -1
- package/dist/assets/workspace-editor-model-DND_Y-Bj.js +1 -0
- package/dist/assets/workspace-editor-monaco-BPWhgcIB.js +4 -0
- package/dist/assets/workspace-editor-monaco-N641qPjX.js +1 -0
- package/dist/assets/workspace-view-state-k1bTlJ_Z.js +13 -0
- package/dist/assets/{xml-wWl1dhkQ.js → xml-BR-HvxT8.js} +1 -1
- package/dist/assets/{yaml-Dc2WDaOZ.js → yaml-YtFQwh4q.js} +1 -1
- package/dist/index.html +2 -1
- package/extensions/browser/backend/agent-browser-runtime.cjs +1040 -0
- package/extensions/browser/backend/agent-capability.cjs +85 -0
- package/extensions/browser/backend/browser-mcp-server.cjs +655 -0
- package/extensions/browser/backend/browser-resource-manager.cjs +1486 -0
- package/extensions/browser/backend/browser-resource-store.cjs +228 -0
- package/extensions/browser/backend/browser-router.cjs +203 -0
- package/extensions/browser/backend/executable-discovery.cjs +248 -0
- package/extensions/browser/backend/farming-browser-client.cjs +116 -0
- package/extensions/browser/backend/index.cjs +32 -0
- package/extensions/browser/backend/managed-chromium-installer.cjs +833 -0
- package/extensions/browser/bin/farming-browser +16 -7
- package/extensions/computer/backend/agent-capability.cjs +72 -0
- package/extensions/computer/backend/computer-constants.cjs +59 -0
- package/extensions/computer/backend/computer-mcp-server.cjs +182 -0
- package/extensions/computer/backend/computer-resource-manager.cjs +1219 -0
- package/extensions/computer/backend/computer-resource-store.cjs +190 -0
- package/extensions/computer/backend/computer-router.cjs +257 -0
- package/extensions/computer/backend/cua-tools.json +3067 -0
- package/extensions/computer/backend/farming-computer-client.cjs +84 -0
- package/extensions/computer/backend/index.cjs +35 -0
- package/extensions/computer/backend/isolated-browser-provider.cjs +157 -0
- package/extensions/computer/bin/farming-computer +131 -0
- package/frontend/farming-net/app.js +255 -262
- package/frontend/ghostty-loader.js +24 -20
- package/frontend/reading-anchor.js +210 -193
- package/frontend/runtime-paths.js +24 -26
- package/frontend/session-bridge.js +90 -111
- package/frontend/session-modal-bridge.js +184 -302
- package/frontend/skin-bridge.js +20 -23
- package/frontend/skins/crt/app.js +7686 -7901
- package/frontend/skins/crt/effects.js +7 -6
- package/frontend/skins/crt/index.html +6 -6
- package/frontend/skins/crt/styles/markdown.css +4 -4
- package/frontend/terminal-bridge.js +174 -181
- package/frontend/terminal-replay.js +320 -345
- package/frontend/theme-loader.js +55 -71
- package/index.html +1 -0
- package/package.json +64 -37
- package/shared/backend-connection-status.js +38 -69
- package/shared/browser-protocol.js +235 -110
- package/shared/websocket-liveness.js +40 -0
- package/backend/acp/packaged-claude-acp.js +0 -13
- package/backend/acp/packaged-codex-acp.js +0 -13
- package/backend/acp/patch-decisions.js +0 -153
- package/backend/acp/permission-security.js +0 -108
- package/backend/acp-session-state.js +0 -644
- package/backend/acp-transcript.js +0 -512
- package/backend/agent-env.js +0 -250
- package/backend/agent-json-stream.js +0 -268
- package/backend/agent-lifecycle-journal.js +0 -133
- package/backend/agent-memory-report.js +0 -583
- package/backend/agent-order.js +0 -166
- package/backend/agent-provider-session.js +0 -76
- package/backend/agent-runtime-binding.js +0 -174
- package/backend/agent-session-history.js +0 -953
- package/backend/async-cache.js +0 -94
- package/backend/atomic-json-store.js +0 -37
- package/backend/auth.js +0 -251
- package/backend/bundle-update-helper.js +0 -113
- package/backend/chat-runtime.js +0 -28
- package/backend/claude-settings.js +0 -121
- package/backend/cli-agents.js +0 -486
- package/backend/codex-context-window.js +0 -189
- package/backend/codex-models.js +0 -226
- package/backend/codex-rollout-follower.js +0 -326
- package/backend/codex-session-archive.js +0 -65
- package/backend/codex-session-history.js +0 -647
- package/backend/codex-transcript-sanitizer.js +0 -115
- package/backend/codex-transcript.js +0 -2564
- package/backend/command-runner-child.js +0 -67
- package/backend/config-manager.js +0 -1133
- package/backend/control-api.js +0 -455
- package/backend/executable-discovery.js +0 -216
- package/backend/farming-agent-bootstrap.js +0 -49
- package/backend/farming-cli.js +0 -457
- package/backend/farming-net-pass.js +0 -285
- package/backend/farming-net-registry.js +0 -112
- package/backend/farming-net-server.js +0 -298
- package/backend/farming-session-store.js +0 -677
- package/backend/git-worktree-info.js +0 -181
- package/backend/haiku-token.js +0 -772
- package/backend/index-html.js +0 -62
- package/backend/input-parts.js +0 -54
- package/backend/input-routing.js +0 -24
- package/backend/json-cli-runtime.js +0 -275
- package/backend/main-agent-skills.js +0 -305
- package/backend/main-page-session.js +0 -122
- package/backend/native-pty-controller-generation.js +0 -106
- package/backend/native-pty-host-identity.js +0 -86
- package/backend/native-pty-host-path.js +0 -42
- package/backend/native-session-engine.js +0 -268
- package/backend/network.js +0 -23
- package/backend/npm-update-helper.js +0 -374
- package/backend/packaged-node-pty.js +0 -99
- package/backend/preview-session-manager.js +0 -68
- package/backend/provider-adapters.js +0 -399
- package/backend/provider-session-id.js +0 -47
- package/backend/provider-session-service.js +0 -345
- package/backend/qr-share-tickets.js +0 -100
- package/backend/review-diff-router.js +0 -180
- package/backend/review-diff-service.js +0 -1270
- package/backend/review-session-router.js +0 -74
- package/backend/review-session-service.js +0 -446
- package/backend/review-session-store.js +0 -151
- package/backend/review-state-router.js +0 -116
- package/backend/review-state-store.js +0 -286
- package/backend/run-history-store.js +0 -67
- package/backend/runtime-dependency-manager.js +0 -759
- package/backend/runtime-executable-invocation.js +0 -20
- package/backend/runtime-observation.js +0 -81
- package/backend/server-process-identity.js +0 -47
- package/backend/server.js +0 -3267
- package/backend/session-engine-bridge.js +0 -129
- package/backend/session-engine-router.js +0 -66
- package/backend/session-engine.js +0 -39
- package/backend/session-stream-protocol.js +0 -185
- package/backend/shell-busy-integration.js +0 -472
- package/backend/slash-command-discovery.js +0 -433
- package/backend/storage-layout.js +0 -190
- package/backend/system-monitor.js +0 -77
- package/backend/terminal-attach-checkpoint.js +0 -74
- package/backend/terminal-exit-quiescence.js +0 -39
- package/backend/terminal-runtime-cleanup.js +0 -80
- package/backend/terminal-screen-worker.js +0 -337
- package/backend/terminal-state-serialization.js +0 -127
- package/backend/theme-manager.js +0 -163
- package/backend/update-service.js +0 -1545
- package/backend/usage-forecast.js +0 -86
- package/backend/usage-history-scanner.ts +0 -2718
- package/backend/usage-history-smoke.js +0 -64
- package/backend/usage-history-worker.js +0 -34
- package/backend/workspace-directory.js +0 -232
- package/backend/workspace-discovery.js +0 -311
- package/backend/workspace-root-registry.js +0 -166
- package/dist/assets/App-BKNUILuL.css +0 -1
- package/dist/assets/App-Ex2oVVXO.js +0 -693
- package/dist/assets/FileEditorPane-CNtuGjQ3.js +0 -2
- package/dist/assets/IconGlyphs-BClgag6E.js +0 -1
- package/dist/assets/ProjectFilesSection-Cf2BEcAN.js +0 -12
- package/dist/assets/ReviewPage-BRuxFaH5.js +0 -3
- package/dist/assets/code-dark-BEZTQwYZ.css +0 -1
- package/dist/assets/file-icons-BOlcJ0ZF.js +0 -1
- package/dist/assets/main-DK9Sy7-c.css +0 -1
- package/dist/assets/review-BcnwQtJv.css +0 -1
- package/dist/assets/workspace-editor-model-CSGCl21x.js +0 -1
- package/dist/assets/workspace-editor-monaco-CJNwbpCy.js +0 -4
- package/dist/assets/workspace-editor-monaco-CO9w8YV0.js +0 -1
- package/dist/assets/workspace-view-state-BjKPYp0E.js +0 -7
- package/extensions/browser/backend/agent-browser-runtime.js +0 -1041
- package/extensions/browser/backend/agent-capability.js +0 -55
- package/extensions/browser/backend/browser-mcp-server.js +0 -621
- package/extensions/browser/backend/browser-resource-manager.js +0 -1309
- package/extensions/browser/backend/browser-resource-store.js +0 -190
- package/extensions/browser/backend/browser-router.js +0 -133
- package/extensions/browser/backend/executable-discovery.js +0 -224
- package/extensions/browser/backend/farming-browser-client.js +0 -83
- package/extensions/browser/backend/index.js +0 -7
- package/extensions/browser/frontend/BrowserSidebarPortals.tsx +0 -360
- package/extensions/browser/frontend/BrowserViewer.tsx +0 -500
- package/extensions/browser/frontend/browser-resource-state.ts +0 -60
- package/extensions/browser/frontend/browser.css +0 -567
- package/extensions/browser/frontend/types.ts +0 -31
- package/extensions/browser/frontend/useBrowserResources.ts +0 -158
- package/frontend/vendor/ghostty-web/index.d.ts +0 -1842
- package/shared/backend-connection-status.d.ts +0 -37
- package/shared/browser-protocol.d.ts +0 -5
- /package/dist/assets/{api-w2x5QfxB.js → api-ByCV9MEr.js} +0 -0
- /package/dist/assets/{core-vPkV8P_m.js → core-Czwdx12k.js} +0 -0
- /package/dist/assets/{fsharp-DNZNZyEE.js → fsharp-BjPpkoJe.js} +0 -0
- /package/dist/assets/{go-_kPJjNc_.js → go-BkIDmSUP.js} +0 -0
- /package/dist/assets/{graphql-4HCNVt5g.js → graphql-CCJTpqi1.js} +0 -0
- /package/dist/assets/{hcl-C87ymY0o.js → hcl-s4z8lUFg.js} +0 -0
- /package/dist/assets/{ini-BYrrGNTc.js → ini-CyUW1I7Z.js} +0 -0
- /package/dist/assets/{java-B211rJbk.js → java-CFCcGRiM.js} +0 -0
- /package/dist/assets/{julia-DVPsRtBm.js → julia-DQyH_EYb.js} +0 -0
- /package/dist/assets/{kotlin-BYyNerjp.js → kotlin-B8hA5O51.js} +0 -0
- /package/dist/assets/{less-EC9UblGE.js → less-DaVdaas0.js} +0 -0
- /package/dist/assets/{lexon-B1yuUdqW.js → lexon-DLBhPd3y.js} +0 -0
- /package/dist/assets/{lua-Bo6GFzxT.js → lua-gB1c_cif.js} +0 -0
- /package/dist/assets/{m3-C-Y9cBVI.js → m3-C2T3z3gB.js} +0 -0
- /package/dist/assets/{markdown-BHu-wLbe.js → markdown-CKq-Euj1.js} +0 -0
- /package/dist/assets/{mips-Dkds26rX.js → mips-Bcs5qIDX.js} +0 -0
- /package/dist/assets/{msdax-PU4g-8_g.js → msdax-CBP48FHF.js} +0 -0
- /package/dist/assets/{mysql-fPpuGpS7.js → mysql-c7tiPcFj.js} +0 -0
- /package/dist/assets/{objective-c-Wy1e5JUf.js → objective-c-D4BZkeEV.js} +0 -0
- /package/dist/assets/{pascal-BIXwvrqU.js → pascal-CJDZg0MN.js} +0 -0
- /package/dist/assets/{pascaligo-DwR0AtL_.js → pascaligo-Bygi8Ynl.js} +0 -0
- /package/dist/assets/{perl-CGHAl8my.js → perl-aS8bkf7E.js} +0 -0
- /package/dist/assets/{pgsql-B91M73E2.js → pgsql-CzNAYFYl.js} +0 -0
- /package/dist/assets/{php-C70ttrCx.js → php-DOtoEFTT.js} +0 -0
- /package/dist/assets/{pla-BNrXaRh0.js → pla-DugRnao1.js} +0 -0
- /package/dist/assets/{postiats-Dv2IAZR9.js → postiats-DsXwusKh.js} +0 -0
- /package/dist/assets/{powerquery-CkY5Wq7J.js → powerquery-DL3o72oD.js} +0 -0
- /package/dist/assets/{powershell-uGIYLU3C.js → powershell-C1qHekys.js} +0 -0
- /package/dist/assets/{protobuf-CavfmJu-.js → protobuf-DHgsc-cH.js} +0 -0
- /package/dist/assets/{pug-Drqnyitx.js → pug-FZXlE_Iu.js} +0 -0
- /package/dist/assets/{qrcode-7uGsTEmi.js → qrcode-DJDTdjL7.js} +0 -0
- /package/dist/assets/{qsharp-C7yF-Dti.js → qsharp-bLyhI3DT.js} +0 -0
- /package/dist/assets/{r-HXLGP0_-.js → r-DjFGXbAa.js} +0 -0
- /package/dist/assets/{redis-C4y6mf2p.js → redis-DSoZ_V6u.js} +0 -0
- /package/dist/assets/{redshift-DdQrFPrv.js → redshift-CzkdCsrg.js} +0 -0
- /package/dist/assets/{restructuredtext-CBrf2PNj.js → restructuredtext-B_XPrGCQ.js} +0 -0
- /package/dist/assets/{ruby-CVtMoX2B.js → ruby-Cry2RneJ.js} +0 -0
- /package/dist/assets/{rust-Ckq4sbMP.js → rust-CWzdbFq1.js} +0 -0
- /package/dist/assets/{sb-er66OpE3.js → sb-DmofU59h.js} +0 -0
- /package/dist/assets/{scala-BdWZz-hH.js → scala-BUC2l-vN.js} +0 -0
- /package/dist/assets/{scheme-kTV_s2kP.js → scheme-CNIB_72f.js} +0 -0
- /package/dist/assets/{scss-Bymy2VJQ.js → scss-DcL4-NbX.js} +0 -0
- /package/dist/assets/{shell-bwEP1LQ1.js → shell-CgAGvSSA.js} +0 -0
- /package/dist/assets/{solidity-DCddeBgL.js → solidity-DSkt6O_P.js} +0 -0
- /package/dist/assets/{sophia-BMN3AQig.js → sophia--5ytcjDN.js} +0 -0
- /package/dist/assets/{sparql-CSXOLjzi.js → sparql-C1Srr7cD.js} +0 -0
- /package/dist/assets/{sql-COg8ewmK.js → sql-2k_O5fj7.js} +0 -0
- /package/dist/assets/{st-CIaZtBS_.js → st-DCCCPqfz.js} +0 -0
- /package/dist/assets/{swift-DXiOhUHu.js → swift-Dd8mAfOt.js} +0 -0
- /package/dist/assets/{systemverilog-BTDE6Dr2.js → systemverilog-B0-aZYXu.js} +0 -0
- /package/dist/assets/{tcl-B0zy9NvV.js → tcl-CZAZwPVX.js} +0 -0
- /package/dist/assets/{twig-D594l4Z1.js → twig-D-Ag0tdS.js} +0 -0
- /package/dist/assets/{typespec-XKVjaPqo.js → typespec-B7kTFp0Z.js} +0 -0
- /package/dist/assets/{useWorkspaceMenuKeyboard-Bpy_s69Z.js → useWorkspaceMenuKeyboard-uYZpDxhU.js} +0 -0
- /package/dist/assets/{vb-B2wYoKRV.js → vb-20w1JPum.js} +0 -0
- /package/dist/assets/{wgsl-D0Yf2LbK.js → wgsl-CLJfqFt0.js} +0 -0
- /package/dist/assets/{workspace-working-copy-C3FSR3N6.js → workspace-working-copy-Cfz07cOd.js} +0 -0
|
@@ -1,272 +1,317 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
1
|
+
// Generated by scripts/build-backend-runtime.ts. Do not edit.
|
|
2
|
+
"use strict";
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __export = (target, all) => {
|
|
10
|
+
for (var name in all)
|
|
11
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
12
|
+
};
|
|
13
|
+
var __copyProps = (to, from, except, desc) => {
|
|
14
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
15
|
+
for (let key of __getOwnPropNames(from))
|
|
16
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
17
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
18
|
+
}
|
|
19
|
+
return to;
|
|
20
|
+
};
|
|
21
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
22
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
23
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
24
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
25
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
26
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
27
|
+
mod
|
|
28
|
+
));
|
|
29
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
30
|
+
var workspace_file_service_exports = {};
|
|
31
|
+
__export(workspace_file_service_exports, {
|
|
32
|
+
DEFAULT_MAX_FILE_SIZE: () => DEFAULT_MAX_FILE_SIZE,
|
|
33
|
+
DEFAULT_MAX_WRITE_SIZE: () => DEFAULT_MAX_WRITE_SIZE,
|
|
34
|
+
DEFAULT_WATCH_DEPTH: () => DEFAULT_WATCH_DEPTH,
|
|
35
|
+
WorkspaceFileError: () => WorkspaceFileError,
|
|
36
|
+
WorkspaceFileService: () => WorkspaceFileService,
|
|
37
|
+
createAddedFileLineChangesHunk: () => createAddedFileLineChangesHunk,
|
|
38
|
+
isPackagedRuntime: () => isPackagedRuntime,
|
|
39
|
+
normalizeGitHistoryLimit: () => normalizeGitHistoryLimit,
|
|
40
|
+
normalizeGitHistorySkip: () => normalizeGitHistorySkip,
|
|
41
|
+
parseGitBlamePorcelain: () => parseGitBlamePorcelain,
|
|
42
|
+
parseGitHistoryChanges: () => parseGitHistoryChanges,
|
|
43
|
+
parseGitHistoryLog: () => parseGitHistoryLog,
|
|
44
|
+
parseGitHistoryReferences: () => parseGitHistoryReferences,
|
|
45
|
+
parseUnifiedDiffHunks: () => parseUnifiedDiffHunks,
|
|
46
|
+
parseUnifiedDiffRows: () => parseUnifiedDiffRows,
|
|
47
|
+
resolveCommandRunnerNodePath: () => resolveCommandRunnerNodePath,
|
|
48
|
+
selectUnifiedDiffHunk: () => selectUnifiedDiffHunk
|
|
49
|
+
});
|
|
50
|
+
module.exports = __toCommonJS(workspace_file_service_exports);
|
|
51
|
+
const crypto = require("crypto");
|
|
52
|
+
const fs = require("fs");
|
|
53
|
+
const fsp = require("fs/promises");
|
|
54
|
+
const path = require("path");
|
|
55
|
+
const { diffChars } = require("diff");
|
|
56
|
+
const { execFile, spawn } = require("child_process");
|
|
57
|
+
const readline = require("readline");
|
|
58
|
+
const { pathToFileURL } = require("url");
|
|
10
59
|
const DEFAULT_MAX_FILE_SIZE = 1024 * 1024;
|
|
11
60
|
const DEFAULT_MAX_WRITE_SIZE = 2 * 1024 * 1024;
|
|
12
61
|
const DEFAULT_MAX_PREVIEW_FILE_SIZE = 8 * 1024 * 1024;
|
|
13
62
|
const DEFAULT_SEARCH_LIMIT = 100;
|
|
14
63
|
const DEFAULT_GIT_CHANGES_LIMIT = 500;
|
|
15
|
-
const DEFAULT_GIT_STATUS_CACHE_TTL_MS =
|
|
64
|
+
const DEFAULT_GIT_STATUS_CACHE_TTL_MS = 3e4;
|
|
16
65
|
const DEFAULT_GIT_STATUS_INLINE_TIMEOUT_MS = 80;
|
|
17
|
-
const DEFAULT_GIT_STATUS_TIMEOUT_MS =
|
|
18
|
-
const DEFAULT_SEARCH_TIMEOUT_MS =
|
|
19
|
-
const DEFAULT_BLAME_TIMEOUT_MS =
|
|
20
|
-
const DEFAULT_DIFF_TIMEOUT_MS =
|
|
66
|
+
const DEFAULT_GIT_STATUS_TIMEOUT_MS = 15e3;
|
|
67
|
+
const DEFAULT_SEARCH_TIMEOUT_MS = 3e3;
|
|
68
|
+
const DEFAULT_BLAME_TIMEOUT_MS = 5e3;
|
|
69
|
+
const DEFAULT_DIFF_TIMEOUT_MS = 5e3;
|
|
21
70
|
const DEFAULT_DIFF_MAX_BUFFER = 1024 * 1024;
|
|
22
71
|
const DEFAULT_GIT_HISTORY_LIMIT = 50;
|
|
23
72
|
const MAX_GIT_HISTORY_LIMIT = 100;
|
|
24
|
-
const DEFAULT_GIT_HISTORY_TIMEOUT_MS =
|
|
73
|
+
const DEFAULT_GIT_HISTORY_TIMEOUT_MS = 5e3;
|
|
25
74
|
const DEFAULT_GIT_HISTORY_MAX_BUFFER = 8 * 1024 * 1024;
|
|
26
75
|
const DEFAULT_WATCH_DEPTH = 1;
|
|
27
76
|
const SEARCH_FILE_LIST_MAX_BUFFER = 16 * 1024 * 1024;
|
|
28
77
|
const BINARY_SNIFF_BYTES = 8192;
|
|
29
78
|
const PATH_SEARCH_MIN_CANDIDATES = 120;
|
|
30
79
|
const PATH_SEARCH_CANDIDATE_MULTIPLIER = 8;
|
|
31
|
-
const TIMEOUT = Symbol(
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
[
|
|
41
|
-
[
|
|
80
|
+
const TIMEOUT = /* @__PURE__ */ Symbol("timeout");
|
|
81
|
+
function processError(value) {
|
|
82
|
+
if (value instanceof Error) return value;
|
|
83
|
+
return Object.assign(new Error(String(value)), { cause: value });
|
|
84
|
+
}
|
|
85
|
+
function isRecord(value) {
|
|
86
|
+
return typeof value === "object" && value !== null;
|
|
87
|
+
}
|
|
88
|
+
const IMAGE_PREVIEW_MEDIA_TYPES = /* @__PURE__ */ new Map([
|
|
89
|
+
[".png", "image/png"],
|
|
90
|
+
[".jpg", "image/jpeg"],
|
|
91
|
+
[".jpeg", "image/jpeg"],
|
|
92
|
+
[".gif", "image/gif"],
|
|
93
|
+
[".webp", "image/webp"],
|
|
94
|
+
[".bmp", "image/bmp"],
|
|
95
|
+
[".ico", "image/x-icon"],
|
|
96
|
+
[".avif", "image/avif"],
|
|
97
|
+
[".svg", "image/svg+xml"]
|
|
42
98
|
]);
|
|
43
|
-
const TEXT_IMAGE_PREVIEW_EXTENSIONS = new Set([
|
|
44
|
-
const IGNORED_NAMES = new Set([
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
99
|
+
const TEXT_IMAGE_PREVIEW_EXTENSIONS = /* @__PURE__ */ new Set([".svg"]);
|
|
100
|
+
const IGNORED_NAMES = /* @__PURE__ */ new Set([
|
|
101
|
+
".git",
|
|
102
|
+
".farming",
|
|
103
|
+
"node_modules",
|
|
104
|
+
"dist",
|
|
105
|
+
"build",
|
|
106
|
+
"coverage",
|
|
107
|
+
".next",
|
|
108
|
+
".vite"
|
|
53
109
|
]);
|
|
54
|
-
const SEARCH_IGNORED_NAMES = new Set([
|
|
110
|
+
const SEARCH_IGNORED_NAMES = /* @__PURE__ */ new Set([
|
|
55
111
|
...IGNORED_NAMES,
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
112
|
+
".dolt",
|
|
113
|
+
".doltcfg",
|
|
114
|
+
".idea",
|
|
115
|
+
".vscode",
|
|
116
|
+
".tmp",
|
|
117
|
+
".DS_Store",
|
|
118
|
+
"dist-release",
|
|
119
|
+
"reference",
|
|
120
|
+
"test-results",
|
|
121
|
+
"playwright-report",
|
|
122
|
+
"server.log",
|
|
123
|
+
"local-farming.log",
|
|
124
|
+
".turbo",
|
|
125
|
+
".cache",
|
|
126
|
+
".pytest_cache",
|
|
127
|
+
".mypy_cache",
|
|
128
|
+
".ruff_cache",
|
|
129
|
+
"__pycache__"
|
|
74
130
|
]);
|
|
75
|
-
const EXPANDED_SEARCH_IGNORED_NAMES = new Set([
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
131
|
+
const EXPANDED_SEARCH_IGNORED_NAMES = /* @__PURE__ */ new Set([
|
|
132
|
+
".git",
|
|
133
|
+
".DS_Store",
|
|
134
|
+
"server.log",
|
|
135
|
+
"local-farming.log",
|
|
136
|
+
"node_modules",
|
|
137
|
+
"dist",
|
|
138
|
+
"build",
|
|
139
|
+
"coverage",
|
|
140
|
+
".next",
|
|
141
|
+
".vite",
|
|
142
|
+
".turbo",
|
|
143
|
+
".cache",
|
|
144
|
+
".pytest_cache",
|
|
145
|
+
".mypy_cache",
|
|
146
|
+
".ruff_cache",
|
|
147
|
+
"__pycache__",
|
|
148
|
+
"test-results",
|
|
149
|
+
"playwright-report"
|
|
94
150
|
]);
|
|
95
|
-
const HIDDEN_NAMES = new Set([
|
|
151
|
+
const HIDDEN_NAMES = /* @__PURE__ */ new Set([
|
|
96
152
|
...IGNORED_NAMES,
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
153
|
+
".dolt",
|
|
154
|
+
".doltcfg",
|
|
155
|
+
".idea",
|
|
156
|
+
".vscode",
|
|
157
|
+
".tmp",
|
|
158
|
+
".DS_Store",
|
|
159
|
+
"dist-release",
|
|
160
|
+
"reference",
|
|
161
|
+
"test-results",
|
|
162
|
+
"playwright-report",
|
|
163
|
+
"server.log",
|
|
164
|
+
"local-farming.log",
|
|
165
|
+
".turbo",
|
|
166
|
+
".cache",
|
|
167
|
+
".pytest_cache",
|
|
168
|
+
".mypy_cache",
|
|
169
|
+
".ruff_cache",
|
|
170
|
+
"__pycache__"
|
|
115
171
|
]);
|
|
116
|
-
const TREE_HIDDEN_NAMES = new Set([
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
172
|
+
const TREE_HIDDEN_NAMES = /* @__PURE__ */ new Set([
|
|
173
|
+
".git",
|
|
174
|
+
".DS_Store",
|
|
175
|
+
"server.log",
|
|
176
|
+
"local-farming.log"
|
|
121
177
|
]);
|
|
122
|
-
|
|
123
178
|
function gitDiffWhitespaceArgs(value) {
|
|
124
|
-
if (value ===
|
|
125
|
-
if (value ===
|
|
126
|
-
if (value ===
|
|
179
|
+
if (value === "ALL" || value === "IGNORE_ALL") return ["--ignore-all-space"];
|
|
180
|
+
if (value === "TRAILING" || value === "IGNORE_TRAILING") return ["--ignore-space-at-eol"];
|
|
181
|
+
if (value === "LEADING_AND_TRAILING" || value === "IGNORE_LEADING_AND_TRAILING") return ["--ignore-space-change"];
|
|
127
182
|
return [];
|
|
128
183
|
}
|
|
129
|
-
|
|
130
184
|
function gitDiffContextArgs(value) {
|
|
131
185
|
const context = Number(value);
|
|
132
186
|
if (!Number.isInteger(context) || context < 0) return [];
|
|
133
|
-
return [`--unified=${Math.min(context,
|
|
187
|
+
return [`--unified=${Math.min(context, 1e4)}`];
|
|
134
188
|
}
|
|
135
|
-
|
|
136
189
|
function resolveBundledRipgrepPath() {
|
|
137
|
-
const script = path.join(__dirname,
|
|
190
|
+
const script = path.join(__dirname, "..", "node_modules", "ripgrep", "lib", "rg.mjs");
|
|
138
191
|
if (fs.existsSync(script)) return script;
|
|
139
|
-
const binaryName = process.platform ===
|
|
140
|
-
const bin = path.join(__dirname,
|
|
141
|
-
return fs.existsSync(bin) ? bin :
|
|
192
|
+
const binaryName = process.platform === "win32" ? "rg.cmd" : "rg";
|
|
193
|
+
const bin = path.join(__dirname, "..", "node_modules", ".bin", binaryName);
|
|
194
|
+
return fs.existsSync(bin) ? bin : "";
|
|
142
195
|
}
|
|
143
|
-
|
|
144
196
|
function resolveBundledRipgrepModulePath() {
|
|
145
197
|
try {
|
|
146
|
-
return require.resolve(
|
|
198
|
+
return require.resolve("ripgrep");
|
|
147
199
|
} catch {
|
|
148
|
-
const candidate = path.join(__dirname,
|
|
149
|
-
return fs.existsSync(candidate) ? candidate :
|
|
200
|
+
const candidate = path.join(__dirname, "..", "node_modules", "ripgrep", "lib", "index.mjs");
|
|
201
|
+
return fs.existsSync(candidate) ? candidate : "";
|
|
150
202
|
}
|
|
151
203
|
}
|
|
152
|
-
|
|
153
204
|
function isCommandUnavailable(error) {
|
|
154
|
-
return error && (error.code ===
|
|
205
|
+
return error instanceof Error && "code" in error && (error.code === "ENOENT" || error.code === "EACCES");
|
|
155
206
|
}
|
|
156
|
-
|
|
157
207
|
class WorkspaceFileError extends Error {
|
|
208
|
+
details;
|
|
209
|
+
statusCode;
|
|
158
210
|
constructor(message, statusCode = 400, details = {}) {
|
|
159
211
|
super(message);
|
|
160
|
-
this.name =
|
|
212
|
+
this.name = "WorkspaceFileError";
|
|
161
213
|
this.statusCode = statusCode;
|
|
162
214
|
this.details = details;
|
|
163
215
|
}
|
|
164
216
|
}
|
|
165
|
-
|
|
166
217
|
function resolveCommandRunnerNodePath(options = {}) {
|
|
167
218
|
return options.nodePath || process.env.FARMING_NODE_BIN || process.execPath;
|
|
168
219
|
}
|
|
169
|
-
|
|
170
220
|
function isPackagedRuntime() {
|
|
171
|
-
return Boolean(process.pkg) || process.env.FARMING_PACKAGED_RUNTIME ===
|
|
221
|
+
return Boolean(process.pkg) || process.env.FARMING_PACKAGED_RUNTIME === "1";
|
|
172
222
|
}
|
|
173
|
-
|
|
174
223
|
let chokidarPromise = null;
|
|
175
|
-
|
|
176
224
|
async function loadChokidar() {
|
|
177
225
|
if (!chokidarPromise) {
|
|
178
|
-
chokidarPromise = import(
|
|
226
|
+
chokidarPromise = import("chokidar").then((module2) => module2.default || module2);
|
|
179
227
|
}
|
|
180
228
|
return chokidarPromise;
|
|
181
229
|
}
|
|
182
|
-
|
|
183
230
|
class CommandRunner {
|
|
231
|
+
child;
|
|
232
|
+
helperPath;
|
|
233
|
+
nextId;
|
|
234
|
+
nodePath;
|
|
235
|
+
pending;
|
|
236
|
+
ready;
|
|
184
237
|
constructor(options = {}) {
|
|
185
|
-
this.helperPath = options.helperPath || path.join(__dirname,
|
|
238
|
+
this.helperPath = options.helperPath || path.join(__dirname, "command-runner-child.cjs");
|
|
186
239
|
this.nodePath = resolveCommandRunnerNodePath(options);
|
|
187
240
|
this.nextId = 1;
|
|
188
|
-
this.pending = new Map();
|
|
241
|
+
this.pending = /* @__PURE__ */ new Map();
|
|
189
242
|
this.child = null;
|
|
190
243
|
this.ready = false;
|
|
191
244
|
if (!options.disableHelper && !isPackagedRuntime()) {
|
|
192
245
|
this.start();
|
|
193
246
|
}
|
|
194
247
|
}
|
|
195
|
-
|
|
196
248
|
start() {
|
|
197
249
|
try {
|
|
198
250
|
this.child = spawn(this.nodePath, [this.helperPath], {
|
|
199
251
|
cwd: __dirname,
|
|
200
252
|
env: process.env,
|
|
201
|
-
stdio: [
|
|
253
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
202
254
|
});
|
|
203
255
|
} catch {
|
|
204
256
|
this.child = null;
|
|
205
257
|
return;
|
|
206
258
|
}
|
|
207
|
-
|
|
259
|
+
const child = this.child;
|
|
260
|
+
if (!child) return;
|
|
208
261
|
this.ready = true;
|
|
209
262
|
const rl = readline.createInterface({
|
|
210
|
-
input:
|
|
211
|
-
crlfDelay: Infinity
|
|
263
|
+
input: child.stdout,
|
|
264
|
+
crlfDelay: Infinity
|
|
212
265
|
});
|
|
213
|
-
|
|
214
|
-
rl.on('line', line => {
|
|
266
|
+
rl.on("line", (line) => {
|
|
215
267
|
let response;
|
|
216
268
|
try {
|
|
217
269
|
response = JSON.parse(line);
|
|
218
270
|
} catch {
|
|
219
271
|
return;
|
|
220
272
|
}
|
|
221
|
-
|
|
222
273
|
const pending = this.pending.get(response.id);
|
|
223
274
|
if (!pending) return;
|
|
224
275
|
this.pending.delete(response.id);
|
|
225
|
-
|
|
226
276
|
if (response.ok) {
|
|
227
277
|
pending.resolve({
|
|
228
|
-
stdout: response.stdout ||
|
|
229
|
-
stderr: response.stderr ||
|
|
278
|
+
stdout: response.stdout || "",
|
|
279
|
+
stderr: response.stderr || ""
|
|
230
280
|
});
|
|
231
281
|
return;
|
|
232
282
|
}
|
|
233
|
-
|
|
234
|
-
const error = new Error(response.error?.message || 'command failed');
|
|
283
|
+
const error = new Error(response.error?.message || "command failed");
|
|
235
284
|
error.code = response.error?.code;
|
|
236
285
|
error.signal = response.error?.signal;
|
|
237
|
-
error.stdout = response.error?.stdout ||
|
|
238
|
-
error.stderr = response.error?.stderr ||
|
|
286
|
+
error.stdout = response.error?.stdout || "";
|
|
287
|
+
error.stderr = response.error?.stderr || "";
|
|
239
288
|
pending.reject(error);
|
|
240
289
|
});
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
if (text) console.error('Workspace command helper:', text);
|
|
290
|
+
child.stderr.on("data", (data) => {
|
|
291
|
+
const text = String(data || "").trim();
|
|
292
|
+
if (text) console.error("Workspace command helper:", text);
|
|
245
293
|
});
|
|
246
|
-
|
|
247
|
-
this.child.on('error', () => {
|
|
294
|
+
child.on("error", () => {
|
|
248
295
|
this.ready = false;
|
|
249
296
|
this.child = null;
|
|
250
297
|
const pending = Array.from(this.pending.values());
|
|
251
298
|
this.pending.clear();
|
|
252
|
-
pending.forEach(({ reject }) => reject(new Error(
|
|
299
|
+
pending.forEach(({ reject }) => reject(new Error("workspace command helper failed")));
|
|
253
300
|
});
|
|
254
|
-
|
|
255
|
-
this.child.on('exit', () => {
|
|
301
|
+
child.on("exit", () => {
|
|
256
302
|
this.ready = false;
|
|
257
303
|
this.child = null;
|
|
258
304
|
const pending = Array.from(this.pending.values());
|
|
259
305
|
this.pending.clear();
|
|
260
|
-
pending.forEach(({ reject }) => reject(new Error(
|
|
306
|
+
pending.forEach(({ reject }) => reject(new Error("workspace command helper exited")));
|
|
261
307
|
});
|
|
262
308
|
}
|
|
263
|
-
|
|
264
309
|
run(command, args, options = {}) {
|
|
265
310
|
if (!this.ready || !this.child || !this.child.stdin.writable) {
|
|
266
311
|
return execFileAsync(command, args, options);
|
|
267
312
|
}
|
|
268
|
-
|
|
269
313
|
const id = String(this.nextId++);
|
|
314
|
+
const child = this.child;
|
|
270
315
|
const request = {
|
|
271
316
|
id,
|
|
272
317
|
command,
|
|
@@ -274,19 +319,18 @@ class CommandRunner {
|
|
|
274
319
|
cwd: options.cwd,
|
|
275
320
|
env: options.env,
|
|
276
321
|
maxBuffer: options.maxBuffer,
|
|
277
|
-
timeout: options.timeout
|
|
322
|
+
timeout: options.timeout
|
|
278
323
|
};
|
|
279
|
-
|
|
280
324
|
return new Promise((resolve, reject) => {
|
|
281
325
|
this.pending.set(id, { resolve, reject });
|
|
282
|
-
|
|
326
|
+
child.stdin.write(`${JSON.stringify(request)}
|
|
327
|
+
`, (error) => {
|
|
283
328
|
if (!error) return;
|
|
284
329
|
this.pending.delete(id);
|
|
285
330
|
reject(error);
|
|
286
331
|
});
|
|
287
332
|
});
|
|
288
333
|
}
|
|
289
|
-
|
|
290
334
|
dispose() {
|
|
291
335
|
if (!this.child) return;
|
|
292
336
|
this.child.kill();
|
|
@@ -294,76 +338,61 @@ class CommandRunner {
|
|
|
294
338
|
this.ready = false;
|
|
295
339
|
}
|
|
296
340
|
}
|
|
297
|
-
|
|
298
341
|
function isInside(root, target) {
|
|
299
342
|
const relative = path.relative(root, target);
|
|
300
|
-
return relative ===
|
|
343
|
+
return relative === "" || Boolean(relative) && !relative.startsWith("..") && !path.isAbsolute(relative);
|
|
301
344
|
}
|
|
302
|
-
|
|
303
345
|
function isInsideAnyRoot(roots, target) {
|
|
304
346
|
return (Array.isArray(roots) ? roots : []).some((root) => {
|
|
305
347
|
let realRoot = root;
|
|
306
348
|
try {
|
|
307
349
|
realRoot = fs.realpathSync(root);
|
|
308
350
|
} catch {
|
|
309
|
-
// Missing allowed roots cannot authorize a resolved target.
|
|
310
351
|
}
|
|
311
352
|
return isInside(realRoot, target);
|
|
312
353
|
});
|
|
313
354
|
}
|
|
314
|
-
|
|
315
355
|
function joinRelativePath(parentPath, name) {
|
|
316
|
-
return [String(parentPath ||
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
function normalizeUserPath(userPath = '') {
|
|
323
|
-
const value = String(userPath || '').trim();
|
|
324
|
-
if (!value || value === '.') return '';
|
|
356
|
+
return [String(parentPath || "").replace(/\/+$/, ""), name].filter(Boolean).join("/").replace(/\\/g, "/");
|
|
357
|
+
}
|
|
358
|
+
function normalizeUserPath(userPath = "") {
|
|
359
|
+
const value = String(userPath || "").trim();
|
|
360
|
+
if (!value || value === ".") return "";
|
|
325
361
|
if (path.isAbsolute(value)) {
|
|
326
|
-
throw new WorkspaceFileError(
|
|
362
|
+
throw new WorkspaceFileError("absolute paths are not allowed", 400);
|
|
327
363
|
}
|
|
328
364
|
const normalized = path.normalize(value);
|
|
329
|
-
if (normalized ===
|
|
330
|
-
throw new WorkspaceFileError(
|
|
365
|
+
if (normalized === ".." || normalized.startsWith(`..${path.sep}`)) {
|
|
366
|
+
throw new WorkspaceFileError("path must stay inside the workspace", 403);
|
|
331
367
|
}
|
|
332
|
-
return normalized ===
|
|
368
|
+
return normalized === "." ? "" : normalized;
|
|
333
369
|
}
|
|
334
|
-
|
|
335
370
|
function relativeFromRoot(root, target) {
|
|
336
371
|
const relative = path.relative(root, target);
|
|
337
|
-
return relative.split(path.sep).join(
|
|
372
|
+
return relative.split(path.sep).join("/");
|
|
338
373
|
}
|
|
339
|
-
|
|
340
374
|
function normalizeSearchResultPath(resultPath) {
|
|
341
|
-
return String(resultPath ||
|
|
375
|
+
return String(resultPath || "").replace(/^\.\//, "");
|
|
342
376
|
}
|
|
343
|
-
|
|
344
377
|
function searchIgnoredNames(includeIgnored = false) {
|
|
345
378
|
return includeIgnored ? EXPANDED_SEARCH_IGNORED_NAMES : SEARCH_IGNORED_NAMES;
|
|
346
379
|
}
|
|
347
|
-
|
|
348
380
|
function searchExcludeGlobArgs() {
|
|
349
381
|
const args = [];
|
|
350
382
|
SEARCH_IGNORED_NAMES.forEach((name) => {
|
|
351
|
-
args.push(
|
|
352
|
-
args.push(
|
|
383
|
+
args.push("--glob", `!${name}/**`);
|
|
384
|
+
args.push("--glob", `!**/${name}/**`);
|
|
353
385
|
});
|
|
354
386
|
return args;
|
|
355
387
|
}
|
|
356
|
-
|
|
357
388
|
function isSearchIgnoredRelativePath(relativePath, includeIgnored = false) {
|
|
358
|
-
return String(relativePath ||
|
|
389
|
+
return String(relativePath || "").split(/[\\/]/).some((part) => searchIgnoredNames(includeIgnored).has(part));
|
|
359
390
|
}
|
|
360
|
-
|
|
361
391
|
function shouldPruneDirectoryNameSearch(relativePath) {
|
|
362
|
-
return String(relativePath ||
|
|
392
|
+
return String(relativePath || "").split(/[\\/]/).some((part) => IGNORED_NAMES.has(part));
|
|
363
393
|
}
|
|
364
|
-
|
|
365
394
|
function gitStatusExcludePathspecArgs() {
|
|
366
|
-
const args = [
|
|
395
|
+
const args = ["--", "."];
|
|
367
396
|
HIDDEN_NAMES.forEach((name) => {
|
|
368
397
|
args.push(`:(exclude)${name}`);
|
|
369
398
|
args.push(`:(exclude)${name}/**`);
|
|
@@ -372,9 +401,8 @@ function gitStatusExcludePathspecArgs() {
|
|
|
372
401
|
});
|
|
373
402
|
return args;
|
|
374
403
|
}
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
const args = ['--', searchPath];
|
|
404
|
+
function gitSearchExcludePathspecArgs(includeIgnored = false, searchPath = ".") {
|
|
405
|
+
const args = ["--", searchPath];
|
|
378
406
|
searchIgnoredNames(includeIgnored).forEach((name) => {
|
|
379
407
|
args.push(`:(exclude)${name}`);
|
|
380
408
|
args.push(`:(exclude)${name}/**`);
|
|
@@ -383,27 +411,22 @@ function gitSearchExcludePathspecArgs(includeIgnored = false, searchPath = '.')
|
|
|
383
411
|
});
|
|
384
412
|
return args;
|
|
385
413
|
}
|
|
386
|
-
|
|
387
414
|
function normalizeGitStatusPath(resultPath) {
|
|
388
|
-
return normalizeSearchResultPath(resultPath).replace(/\\/g,
|
|
415
|
+
return normalizeSearchResultPath(resultPath).replace(/\\/g, "/").replace(/^\/+|\/+$/g, "");
|
|
389
416
|
}
|
|
390
|
-
|
|
391
417
|
function sha1(buffer) {
|
|
392
|
-
return crypto.createHash(
|
|
418
|
+
return crypto.createHash("sha1").update(buffer).digest("hex");
|
|
393
419
|
}
|
|
394
|
-
|
|
395
420
|
function previewForPath(filePath, options = {}) {
|
|
396
421
|
const extension = path.extname(filePath).toLowerCase();
|
|
397
|
-
if (extension ===
|
|
422
|
+
if (extension === ".pdf") return { kind: "pdf", mediaType: "application/pdf" };
|
|
398
423
|
if (!options.includeTextImages && TEXT_IMAGE_PREVIEW_EXTENSIONS.has(extension)) return null;
|
|
399
424
|
const mediaType = IMAGE_PREVIEW_MEDIA_TYPES.get(extension);
|
|
400
|
-
return mediaType ? { kind:
|
|
425
|
+
return mediaType ? { kind: "image", mediaType } : null;
|
|
401
426
|
}
|
|
402
|
-
|
|
403
427
|
function metadataFileVersion(relativePath, stat) {
|
|
404
|
-
return sha1(Buffer.from(`${relativePath}:${stat.size}:${stat.mtimeMs}`,
|
|
428
|
+
return sha1(Buffer.from(`${relativePath}:${stat.size}:${stat.mtimeMs}`, "utf8"));
|
|
405
429
|
}
|
|
406
|
-
|
|
407
430
|
function workspaceEntryVersion(stat) {
|
|
408
431
|
return sha1(Buffer.from([
|
|
409
432
|
stat.dev,
|
|
@@ -411,28 +434,25 @@ function workspaceEntryVersion(stat) {
|
|
|
411
434
|
stat.mode,
|
|
412
435
|
stat.size,
|
|
413
436
|
stat.mtimeMs,
|
|
414
|
-
stat.ctimeMs
|
|
415
|
-
].join(
|
|
437
|
+
stat.ctimeMs
|
|
438
|
+
].join(":"), "utf8"));
|
|
416
439
|
}
|
|
417
|
-
|
|
418
440
|
async function readTextPrefix(target, maxBytes) {
|
|
419
|
-
const handle = await fsp.open(target,
|
|
441
|
+
const handle = await fsp.open(target, "r");
|
|
420
442
|
try {
|
|
421
443
|
const buffer = Buffer.alloc(maxBytes);
|
|
422
444
|
const { bytesRead } = await handle.read(buffer, 0, maxBytes, 0);
|
|
423
|
-
return buffer.subarray(0, bytesRead).toString(
|
|
445
|
+
return buffer.subarray(0, bytesRead).toString("utf8");
|
|
424
446
|
} finally {
|
|
425
447
|
await handle.close();
|
|
426
448
|
}
|
|
427
449
|
}
|
|
428
|
-
|
|
429
450
|
async function isProbablyBinaryFile(target) {
|
|
430
|
-
const handle = await fsp.open(target,
|
|
451
|
+
const handle = await fsp.open(target, "r");
|
|
431
452
|
try {
|
|
432
453
|
const buffer = Buffer.alloc(BINARY_SNIFF_BYTES);
|
|
433
454
|
const { bytesRead } = await handle.read(buffer, 0, BINARY_SNIFF_BYTES, 0);
|
|
434
455
|
if (bytesRead === 0) return false;
|
|
435
|
-
|
|
436
456
|
let suspiciousControlBytes = 0;
|
|
437
457
|
for (let index = 0; index < bytesRead; index += 1) {
|
|
438
458
|
const byte = buffer[index];
|
|
@@ -442,48 +462,41 @@ async function isProbablyBinaryFile(target) {
|
|
|
442
462
|
suspiciousControlBytes += 1;
|
|
443
463
|
}
|
|
444
464
|
}
|
|
445
|
-
|
|
446
465
|
return suspiciousControlBytes / bytesRead > 0.3;
|
|
447
466
|
} finally {
|
|
448
467
|
await handle.close();
|
|
449
468
|
}
|
|
450
469
|
}
|
|
451
|
-
|
|
452
470
|
function shouldIgnorePath(filePath) {
|
|
453
|
-
return filePath.split(path.sep).some(part => IGNORED_NAMES.has(part));
|
|
471
|
+
return filePath.split(path.sep).some((part) => IGNORED_NAMES.has(part));
|
|
454
472
|
}
|
|
455
|
-
|
|
456
473
|
function shouldHidePath(filePath) {
|
|
457
|
-
return String(filePath ||
|
|
474
|
+
return String(filePath || "").split(/[\\/]/).some((part) => HIDDEN_NAMES.has(part));
|
|
458
475
|
}
|
|
459
|
-
|
|
460
476
|
function normalizeEntryName(name) {
|
|
461
|
-
const value = String(name ||
|
|
462
|
-
if (!value || value ===
|
|
463
|
-
throw new WorkspaceFileError(
|
|
477
|
+
const value = String(name || "").trim();
|
|
478
|
+
if (!value || value === "." || value === "..") {
|
|
479
|
+
throw new WorkspaceFileError("name is required", 400);
|
|
464
480
|
}
|
|
465
|
-
if (value.includes(
|
|
466
|
-
throw new WorkspaceFileError(
|
|
481
|
+
if (value.includes("/") || value.includes("\\") || path.basename(value) !== value) {
|
|
482
|
+
throw new WorkspaceFileError("name must be a single path segment", 400);
|
|
467
483
|
}
|
|
468
484
|
if (IGNORED_NAMES.has(value)) {
|
|
469
|
-
throw new WorkspaceFileError(
|
|
485
|
+
throw new WorkspaceFileError("ignored paths cannot be changed", 403);
|
|
470
486
|
}
|
|
471
487
|
return value;
|
|
472
488
|
}
|
|
473
|
-
|
|
474
489
|
function parentDirectory(filePath) {
|
|
475
|
-
const normalized = String(filePath ||
|
|
476
|
-
const index = normalized.lastIndexOf(
|
|
477
|
-
return index === -1 ?
|
|
490
|
+
const normalized = String(filePath || "").replace(/\/+$/, "");
|
|
491
|
+
const index = normalized.lastIndexOf("/");
|
|
492
|
+
return index === -1 ? "" : normalized.slice(0, index);
|
|
478
493
|
}
|
|
479
|
-
|
|
480
494
|
function execFileAsync(command, args, options = {}) {
|
|
481
495
|
return new Promise((resolve, reject) => {
|
|
482
496
|
execFile(command, args, {
|
|
483
|
-
encoding:
|
|
497
|
+
encoding: "utf8",
|
|
484
498
|
maxBuffer: 2 * 1024 * 1024,
|
|
485
|
-
|
|
486
|
-
...options,
|
|
499
|
+
...options
|
|
487
500
|
}, (error, stdout, stderr) => {
|
|
488
501
|
if (error) {
|
|
489
502
|
error.stdout = stdout;
|
|
@@ -495,92 +508,83 @@ function execFileAsync(command, args, options = {}) {
|
|
|
495
508
|
});
|
|
496
509
|
});
|
|
497
510
|
}
|
|
498
|
-
|
|
499
511
|
function gitStatusKind(statusCode) {
|
|
500
|
-
|
|
501
|
-
if (
|
|
502
|
-
if (
|
|
503
|
-
if (
|
|
504
|
-
if (
|
|
505
|
-
if (
|
|
506
|
-
if (
|
|
507
|
-
return
|
|
508
|
-
|
|
509
|
-
|
|
512
|
+
const code = String(statusCode || "");
|
|
513
|
+
if (code.length < 2) return "modified";
|
|
514
|
+
if (code === "??") return "untracked";
|
|
515
|
+
if (code.includes("U") || code === "AA" || code === "DD") return "conflicted";
|
|
516
|
+
if (code.includes("D")) return "deleted";
|
|
517
|
+
if (code.includes("A")) return "added";
|
|
518
|
+
if (code.includes("R")) return "renamed";
|
|
519
|
+
if (code.includes("M")) return "modified";
|
|
520
|
+
return "modified";
|
|
521
|
+
}
|
|
510
522
|
function gitStatusLabel(kind) {
|
|
511
|
-
if (kind ===
|
|
512
|
-
if (kind ===
|
|
513
|
-
if (kind ===
|
|
514
|
-
if (kind ===
|
|
515
|
-
if (kind ===
|
|
516
|
-
return
|
|
517
|
-
}
|
|
518
|
-
|
|
523
|
+
if (kind === "added") return "A";
|
|
524
|
+
if (kind === "deleted") return "D";
|
|
525
|
+
if (kind === "renamed") return "R";
|
|
526
|
+
if (kind === "untracked") return "U";
|
|
527
|
+
if (kind === "conflicted") return "!";
|
|
528
|
+
return "M";
|
|
529
|
+
}
|
|
519
530
|
function gitStatusReviewRank(kind) {
|
|
520
|
-
if (kind ===
|
|
521
|
-
if (kind ===
|
|
522
|
-
if (kind ===
|
|
523
|
-
if (kind ===
|
|
524
|
-
if (kind ===
|
|
525
|
-
if (kind ===
|
|
531
|
+
if (kind === "conflicted") return 0;
|
|
532
|
+
if (kind === "modified") return 1;
|
|
533
|
+
if (kind === "added") return 2;
|
|
534
|
+
if (kind === "deleted") return 3;
|
|
535
|
+
if (kind === "renamed") return 4;
|
|
536
|
+
if (kind === "untracked") return 5;
|
|
526
537
|
return 6;
|
|
527
538
|
}
|
|
528
|
-
|
|
529
539
|
function normalizeGitHistoryLimit(value) {
|
|
530
540
|
const parsed = Number(value);
|
|
531
541
|
if (!Number.isInteger(parsed) || parsed < 1) return DEFAULT_GIT_HISTORY_LIMIT;
|
|
532
542
|
return Math.min(parsed, MAX_GIT_HISTORY_LIMIT);
|
|
533
543
|
}
|
|
534
|
-
|
|
535
544
|
function normalizeGitHistorySkip(value) {
|
|
536
545
|
const parsed = Number(value);
|
|
537
546
|
if (!Number.isInteger(parsed) || parsed < 0) return 0;
|
|
538
|
-
return Math.min(parsed,
|
|
547
|
+
return Math.min(parsed, 1e6);
|
|
539
548
|
}
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
const normalized = String(value || '').trim();
|
|
549
|
+
function normalizeGitObjectId(value, fieldName = "commit") {
|
|
550
|
+
const normalized = String(value || "").trim();
|
|
543
551
|
if (!/^[0-9a-f]{40,64}$/i.test(normalized)) {
|
|
544
552
|
throw new WorkspaceFileError(`${fieldName} must be a full git object id`, 400);
|
|
545
553
|
}
|
|
546
554
|
return normalized.toLowerCase();
|
|
547
555
|
}
|
|
548
|
-
|
|
549
556
|
function gitEmptyTreeObjectId(objectFormat) {
|
|
550
|
-
const algorithm = objectFormat ===
|
|
551
|
-
return crypto.createHash(algorithm).update(Buffer.from(
|
|
557
|
+
const algorithm = objectFormat === "sha256" ? "sha256" : "sha1";
|
|
558
|
+
return crypto.createHash(algorithm).update(Buffer.from("tree 0\0")).digest("hex");
|
|
552
559
|
}
|
|
553
|
-
|
|
554
560
|
function gitHistoryReference(value) {
|
|
555
|
-
let id = String(value ||
|
|
561
|
+
let id = String(value || "").trim();
|
|
556
562
|
if (!id) return null;
|
|
557
|
-
if (id.startsWith(
|
|
558
|
-
if (id ===
|
|
559
|
-
if (id.startsWith(
|
|
560
|
-
return { id, name: id.slice(
|
|
563
|
+
if (id.startsWith("tag: ")) id = id.slice(5);
|
|
564
|
+
if (id === "HEAD") return { id, name: id, category: "head" };
|
|
565
|
+
if (id.startsWith("refs/heads/")) {
|
|
566
|
+
return { id, name: id.slice("refs/heads/".length), category: "local-branch" };
|
|
561
567
|
}
|
|
562
|
-
if (id.startsWith(
|
|
568
|
+
if (id.startsWith("refs/remotes/")) {
|
|
563
569
|
if (/\/HEAD$/.test(id)) return null;
|
|
564
|
-
return { id, name: id.slice(
|
|
570
|
+
return { id, name: id.slice("refs/remotes/".length), category: "remote-branch" };
|
|
565
571
|
}
|
|
566
|
-
if (id.startsWith(
|
|
567
|
-
return { id, name: id.slice(
|
|
572
|
+
if (id.startsWith("refs/tags/")) {
|
|
573
|
+
return { id, name: id.slice("refs/tags/".length), category: "tag" };
|
|
568
574
|
}
|
|
569
|
-
return { id, name: id.replace(/^refs\//,
|
|
575
|
+
return { id, name: id.replace(/^refs\//, ""), category: "reference" };
|
|
570
576
|
}
|
|
571
|
-
|
|
572
577
|
function parseGitHistoryReferences(value) {
|
|
573
578
|
const references = [];
|
|
574
|
-
const seen = new Set();
|
|
575
|
-
const add = candidate => {
|
|
579
|
+
const seen = /* @__PURE__ */ new Set();
|
|
580
|
+
const add = (candidate) => {
|
|
576
581
|
const reference = gitHistoryReference(candidate);
|
|
577
582
|
if (!reference || seen.has(reference.id)) return;
|
|
578
583
|
seen.add(reference.id);
|
|
579
584
|
references.push(reference);
|
|
580
585
|
};
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
const arrowIndex = decoration.indexOf(' -> ');
|
|
586
|
+
String(value || "").split(", ").forEach((decoration) => {
|
|
587
|
+
const arrowIndex = decoration.indexOf(" -> ");
|
|
584
588
|
if (arrowIndex === -1) {
|
|
585
589
|
add(decoration);
|
|
586
590
|
return;
|
|
@@ -590,9 +594,8 @@ function parseGitHistoryReferences(value) {
|
|
|
590
594
|
});
|
|
591
595
|
return references;
|
|
592
596
|
}
|
|
593
|
-
|
|
594
597
|
function parseGitHistoryLog(stdout) {
|
|
595
|
-
const fields = String(stdout ||
|
|
598
|
+
const fields = String(stdout || "").split("\0");
|
|
596
599
|
const items = [];
|
|
597
600
|
for (let index = 0; index + 7 < fields.length; index += 8) {
|
|
598
601
|
const id = fields[index];
|
|
@@ -601,170 +604,142 @@ function parseGitHistoryLog(stdout) {
|
|
|
601
604
|
items.push({
|
|
602
605
|
id,
|
|
603
606
|
displayId: id.slice(0, 8),
|
|
604
|
-
parentIds: fields[index + 1].split(
|
|
607
|
+
parentIds: fields[index + 1].split(" ").filter(Boolean),
|
|
605
608
|
author: fields[index + 2],
|
|
606
609
|
authorEmail: fields[index + 3],
|
|
607
|
-
timestamp: Number.isFinite(timestamp) ? timestamp :
|
|
610
|
+
timestamp: Number.isFinite(timestamp) ? timestamp : void 0,
|
|
608
611
|
subject: fields[index + 5],
|
|
609
|
-
message: String(fields[index + 6] ||
|
|
610
|
-
references: parseGitHistoryReferences(fields[index + 7])
|
|
612
|
+
message: String(fields[index + 6] || "").trimEnd(),
|
|
613
|
+
references: parseGitHistoryReferences(fields[index + 7])
|
|
611
614
|
});
|
|
612
615
|
}
|
|
613
616
|
return items;
|
|
614
617
|
}
|
|
615
|
-
|
|
616
618
|
function gitHistoryChangeKind(statusCode) {
|
|
617
|
-
|
|
618
|
-
if (
|
|
619
|
-
if (
|
|
620
|
-
if (
|
|
621
|
-
if (
|
|
622
|
-
return
|
|
623
|
-
|
|
624
|
-
|
|
619
|
+
const code = String(statusCode || "");
|
|
620
|
+
if (code.startsWith("A")) return "added";
|
|
621
|
+
if (code.startsWith("D")) return "deleted";
|
|
622
|
+
if (code.startsWith("R")) return "renamed";
|
|
623
|
+
if (code.startsWith("C")) return "copied";
|
|
624
|
+
if (code.startsWith("T")) return "type-changed";
|
|
625
|
+
return "modified";
|
|
626
|
+
}
|
|
625
627
|
function gitHistoryChangeLabel(kind) {
|
|
626
|
-
if (kind ===
|
|
627
|
-
if (kind ===
|
|
628
|
-
if (kind ===
|
|
629
|
-
if (kind ===
|
|
630
|
-
if (kind ===
|
|
631
|
-
return
|
|
632
|
-
}
|
|
633
|
-
|
|
628
|
+
if (kind === "added") return "A";
|
|
629
|
+
if (kind === "deleted") return "D";
|
|
630
|
+
if (kind === "renamed") return "R";
|
|
631
|
+
if (kind === "copied") return "C";
|
|
632
|
+
if (kind === "type-changed") return "T";
|
|
633
|
+
return "M";
|
|
634
|
+
}
|
|
634
635
|
function parseGitHistoryChanges(stdout) {
|
|
635
|
-
const fields = String(stdout ||
|
|
636
|
+
const fields = String(stdout || "").split("\0");
|
|
636
637
|
const items = [];
|
|
637
|
-
for (let index = 0; index < fields.length;) {
|
|
638
|
+
for (let index = 0; index < fields.length; ) {
|
|
638
639
|
const statusCode = fields[index++];
|
|
639
640
|
if (!statusCode) continue;
|
|
640
641
|
const kind = gitHistoryChangeKind(statusCode);
|
|
641
|
-
if (kind ===
|
|
642
|
-
const previousPath = fields[index++] ||
|
|
643
|
-
const
|
|
644
|
-
if (!
|
|
645
|
-
items.push({ path:
|
|
642
|
+
if (kind === "renamed" || kind === "copied") {
|
|
643
|
+
const previousPath = fields[index++] || "";
|
|
644
|
+
const filePath2 = fields[index++] || "";
|
|
645
|
+
if (!filePath2) continue;
|
|
646
|
+
items.push({ path: filePath2, previousPath, status: kind, statusLabel: gitHistoryChangeLabel(kind) });
|
|
646
647
|
continue;
|
|
647
648
|
}
|
|
648
|
-
const filePath = fields[index++] ||
|
|
649
|
+
const filePath = fields[index++] || "";
|
|
649
650
|
if (!filePath) continue;
|
|
650
651
|
items.push({ path: filePath, status: kind, statusLabel: gitHistoryChangeLabel(kind) });
|
|
651
652
|
}
|
|
652
653
|
return items;
|
|
653
654
|
}
|
|
654
|
-
|
|
655
655
|
async function workspaceEntryTypeForGitChange(root, filePath) {
|
|
656
656
|
try {
|
|
657
|
-
const stat = await fsp.lstat(path.join(root, filePath.replace(/\/+$/,
|
|
658
|
-
if (stat.isDirectory()) return
|
|
659
|
-
if (stat.isFile()) return
|
|
660
|
-
if (stat.isSymbolicLink()) return
|
|
661
|
-
return
|
|
657
|
+
const stat = await fsp.lstat(path.join(root, filePath.replace(/\/+$/, "")));
|
|
658
|
+
if (stat.isDirectory()) return "directory";
|
|
659
|
+
if (stat.isFile()) return "file";
|
|
660
|
+
if (stat.isSymbolicLink()) return "symlink";
|
|
661
|
+
return "other";
|
|
662
662
|
} catch {
|
|
663
|
-
return
|
|
663
|
+
return "file";
|
|
664
664
|
}
|
|
665
665
|
}
|
|
666
|
-
|
|
667
666
|
function gitStatusRank(kind) {
|
|
668
|
-
if (kind ===
|
|
669
|
-
if (kind ===
|
|
670
|
-
if (kind ===
|
|
671
|
-
if (kind ===
|
|
672
|
-
if (kind ===
|
|
673
|
-
if (kind ===
|
|
667
|
+
if (kind === "conflicted") return 6;
|
|
668
|
+
if (kind === "deleted") return 5;
|
|
669
|
+
if (kind === "modified") return 4;
|
|
670
|
+
if (kind === "renamed") return 3;
|
|
671
|
+
if (kind === "added") return 2;
|
|
672
|
+
if (kind === "untracked") return 1;
|
|
674
673
|
return 0;
|
|
675
674
|
}
|
|
676
|
-
|
|
677
675
|
function isPathWordBoundary(text, index) {
|
|
678
676
|
if (index === 0) return true;
|
|
679
|
-
const previous = text[index - 1] ||
|
|
680
|
-
const current = text[index] ||
|
|
677
|
+
const previous = text[index - 1] || "";
|
|
678
|
+
const current = text[index] || "";
|
|
681
679
|
if (/[-_\s./\\]/.test(previous)) return true;
|
|
682
680
|
return /[a-z0-9]/.test(previous) && /[A-Z]/.test(current);
|
|
683
681
|
}
|
|
684
|
-
|
|
685
682
|
function scoreBoundaryMatch(text, normalizedQuery) {
|
|
686
683
|
if (!text || !normalizedQuery || normalizedQuery.length > 6) return null;
|
|
687
684
|
let queryIndex = 0;
|
|
688
685
|
let score = 0;
|
|
689
|
-
|
|
690
686
|
for (let textIndex = 0; textIndex < text.length && queryIndex < normalizedQuery.length; textIndex += 1) {
|
|
691
687
|
if (!isPathWordBoundary(text, textIndex)) continue;
|
|
692
688
|
if (text[textIndex].toLowerCase() !== normalizedQuery[queryIndex]) continue;
|
|
693
689
|
score += textIndex;
|
|
694
690
|
queryIndex += 1;
|
|
695
691
|
}
|
|
696
|
-
|
|
697
692
|
return queryIndex === normalizedQuery.length ? score : null;
|
|
698
693
|
}
|
|
699
|
-
|
|
700
694
|
function scorePathMatch(filePath, query, options = {}) {
|
|
701
695
|
const pathText = normalizeSearchResultPath(filePath);
|
|
702
696
|
const normalizedPath = pathText.toLowerCase();
|
|
703
|
-
const normalizedQuery = String(query ||
|
|
697
|
+
const normalizedQuery = String(query || "").trim().replace(/^\.\/+/, "").toLowerCase();
|
|
704
698
|
if (!normalizedPath || !normalizedQuery) return null;
|
|
705
|
-
|
|
706
699
|
const fileNameText = path.posix.basename(pathText);
|
|
707
700
|
const fileName = fileNameText.toLowerCase();
|
|
708
701
|
if (normalizedPath === normalizedQuery) return 0;
|
|
709
702
|
if (fileName === normalizedQuery) return 1;
|
|
710
|
-
|
|
711
703
|
const nameBoundaryScore = scoreBoundaryMatch(fileNameText, normalizedQuery);
|
|
712
704
|
if (nameBoundaryScore !== null) return 5 + nameBoundaryScore / 100;
|
|
713
|
-
|
|
714
705
|
const nameIndex = fileName.indexOf(normalizedQuery);
|
|
715
706
|
if (nameIndex !== -1) return 10 + nameIndex;
|
|
716
|
-
|
|
717
707
|
if (options.allowPathMatch === false) return null;
|
|
718
|
-
|
|
719
708
|
const pathIndex = normalizedPath.indexOf(normalizedQuery);
|
|
720
709
|
if (pathIndex !== -1) return 40 + pathIndex;
|
|
721
|
-
|
|
722
710
|
if (normalizedQuery.length > 6) return null;
|
|
723
|
-
|
|
724
711
|
let queryIndex = 0;
|
|
725
712
|
let score = 80;
|
|
726
|
-
for (let
|
|
727
|
-
if (normalizedPath[
|
|
728
|
-
score +=
|
|
713
|
+
for (let pathIndex2 = 0; pathIndex2 < normalizedPath.length && queryIndex < normalizedQuery.length; pathIndex2 += 1) {
|
|
714
|
+
if (normalizedPath[pathIndex2] !== normalizedQuery[queryIndex]) continue;
|
|
715
|
+
score += pathIndex2;
|
|
729
716
|
queryIndex += 1;
|
|
730
717
|
}
|
|
731
|
-
|
|
732
718
|
return queryIndex === normalizedQuery.length ? score : null;
|
|
733
719
|
}
|
|
734
|
-
|
|
735
720
|
function pathSearchEntryRank(entryType) {
|
|
736
|
-
return entryType ===
|
|
721
|
+
return entryType === "directory" ? 0 : 1;
|
|
737
722
|
}
|
|
738
|
-
|
|
739
723
|
function pathSearchDepth(filePath) {
|
|
740
|
-
return normalizeSearchResultPath(filePath).split(
|
|
724
|
+
return normalizeSearchResultPath(filePath).split("/").filter(Boolean).length;
|
|
741
725
|
}
|
|
742
|
-
|
|
743
726
|
function comparePathSearchMatches(a, b) {
|
|
744
|
-
return (
|
|
745
|
-
|
|
746
|
-
pathSearchDepth(a.path) - pathSearchDepth(b.path) ||
|
|
747
|
-
pathSearchEntryRank(a.entryType) - pathSearchEntryRank(b.entryType) ||
|
|
748
|
-
a.path.localeCompare(b.path)
|
|
749
|
-
);
|
|
750
|
-
}
|
|
751
|
-
|
|
727
|
+
return (a.score ?? 0) - (b.score ?? 0) || pathSearchDepth(a.path) - pathSearchDepth(b.path) || pathSearchEntryRank(a.entryType) - pathSearchEntryRank(b.entryType) || a.path.localeCompare(b.path);
|
|
728
|
+
}
|
|
752
729
|
function pathSearchMatchForPath(filePath, query, entryType, allowPathMatch) {
|
|
753
730
|
const score = scorePathMatch(filePath, query, { allowPathMatch });
|
|
754
731
|
if (score === null) return null;
|
|
755
732
|
return {
|
|
756
|
-
path: filePath,
|
|
733
|
+
path: normalizeSearchResultPath(filePath),
|
|
757
734
|
entryType,
|
|
758
|
-
score
|
|
735
|
+
score
|
|
759
736
|
};
|
|
760
737
|
}
|
|
761
|
-
|
|
762
738
|
function directoryNameSearchScore(directoryPath, query, allowPathMatch) {
|
|
763
739
|
const pathText = normalizeSearchResultPath(directoryPath);
|
|
764
740
|
const normalizedPath = pathText.toLowerCase();
|
|
765
|
-
const normalizedQuery = String(query ||
|
|
741
|
+
const normalizedQuery = String(query || "").trim().replace(/^\.\/+/, "").toLowerCase();
|
|
766
742
|
if (!normalizedPath || !normalizedQuery) return null;
|
|
767
|
-
|
|
768
743
|
const directoryName = path.posix.basename(pathText).toLowerCase();
|
|
769
744
|
if (normalizedPath === normalizedQuery) return 0;
|
|
770
745
|
if (directoryName === normalizedQuery) return 1;
|
|
@@ -778,45 +753,40 @@ function directoryNameSearchScore(directoryPath, query, allowPathMatch) {
|
|
|
778
753
|
if (pathIndex !== -1) return 40 + pathIndex;
|
|
779
754
|
return null;
|
|
780
755
|
}
|
|
781
|
-
|
|
782
756
|
function directoryNameSearchMatchForPath(directoryPath, query, allowPathMatch) {
|
|
783
757
|
const score = directoryNameSearchScore(directoryPath, query, allowPathMatch);
|
|
784
758
|
if (score === null) return null;
|
|
785
759
|
return {
|
|
786
|
-
path: directoryPath,
|
|
787
|
-
entryType:
|
|
788
|
-
score
|
|
760
|
+
path: normalizeSearchResultPath(directoryPath),
|
|
761
|
+
entryType: "directory",
|
|
762
|
+
score
|
|
789
763
|
};
|
|
790
764
|
}
|
|
791
|
-
|
|
792
765
|
function ancestorDirectoryPaths(filePath) {
|
|
793
|
-
const segments = normalizeSearchResultPath(filePath).split(
|
|
766
|
+
const segments = normalizeSearchResultPath(filePath).split("/").filter(Boolean);
|
|
794
767
|
const ancestors = [];
|
|
795
768
|
for (let index = 1; index < segments.length; index += 1) {
|
|
796
|
-
ancestors.push(segments.slice(0, index).join(
|
|
769
|
+
ancestors.push(segments.slice(0, index).join("/"));
|
|
797
770
|
}
|
|
798
771
|
return ancestors;
|
|
799
772
|
}
|
|
800
|
-
|
|
801
773
|
function pathSearchMatchesForFile(filePath, query, includeIgnored = false) {
|
|
802
774
|
const allowPathMatch = isLikelyPathQuery(query);
|
|
803
775
|
const matches = [];
|
|
804
|
-
const fileMatch = pathSearchMatchForPath(filePath, query,
|
|
776
|
+
const fileMatch = pathSearchMatchForPath(filePath, query, "file", allowPathMatch);
|
|
805
777
|
if (fileMatch) matches.push(fileMatch);
|
|
806
778
|
ancestorDirectoryPaths(filePath).forEach((directoryPath) => {
|
|
807
779
|
if (isSearchIgnoredRelativePath(directoryPath, includeIgnored)) return;
|
|
808
|
-
const directoryMatch = pathSearchMatchForPath(directoryPath, query,
|
|
780
|
+
const directoryMatch = pathSearchMatchForPath(directoryPath, query, "directory", allowPathMatch);
|
|
809
781
|
if (directoryMatch) matches.push(directoryMatch);
|
|
810
782
|
});
|
|
811
783
|
return matches;
|
|
812
784
|
}
|
|
813
|
-
|
|
814
785
|
function isLikelyPathQuery(query) {
|
|
815
|
-
return /[./\\]/.test(String(query ||
|
|
786
|
+
return /[./\\]/.test(String(query || "").trim());
|
|
816
787
|
}
|
|
817
|
-
|
|
818
788
|
function dedupePathMatches(matches, limit) {
|
|
819
|
-
const seen = new Set();
|
|
789
|
+
const seen = /* @__PURE__ */ new Set();
|
|
820
790
|
const deduped = [];
|
|
821
791
|
for (const match of matches) {
|
|
822
792
|
if (!match || seen.has(match.path)) continue;
|
|
@@ -826,110 +796,92 @@ function dedupePathMatches(matches, limit) {
|
|
|
826
796
|
}
|
|
827
797
|
return deduped;
|
|
828
798
|
}
|
|
829
|
-
|
|
830
799
|
function parseGitStatus(stdout) {
|
|
831
|
-
const records = String(stdout ||
|
|
832
|
-
const statusByPath = new Map();
|
|
833
|
-
|
|
800
|
+
const records = String(stdout || "").split("\0").filter(Boolean);
|
|
801
|
+
const statusByPath = /* @__PURE__ */ new Map();
|
|
834
802
|
for (let index = 0; index < records.length; index += 1) {
|
|
835
803
|
const record = records[index];
|
|
836
804
|
if (record.length < 4) continue;
|
|
837
|
-
|
|
838
805
|
const statusCode = record.slice(0, 2);
|
|
839
806
|
const filePath = normalizeGitStatusPath(record.slice(3));
|
|
840
807
|
if (!filePath) continue;
|
|
841
|
-
|
|
842
808
|
const kind = gitStatusKind(statusCode);
|
|
843
|
-
const previousPath = statusCode.includes(
|
|
844
|
-
? normalizeGitStatusPath(records[index + 1])
|
|
845
|
-
: '';
|
|
809
|
+
const previousPath = statusCode.includes("R") || statusCode.includes("C") ? normalizeGitStatusPath(records[index + 1]) : "";
|
|
846
810
|
statusByPath.set(filePath, {
|
|
847
811
|
kind,
|
|
848
812
|
label: gitStatusLabel(kind),
|
|
849
|
-
...
|
|
813
|
+
...previousPath ? { previousPath } : {}
|
|
850
814
|
});
|
|
851
|
-
|
|
852
|
-
if (statusCode.includes('R') || statusCode.includes('C')) {
|
|
815
|
+
if (statusCode.includes("R") || statusCode.includes("C")) {
|
|
853
816
|
index += 1;
|
|
854
817
|
}
|
|
855
818
|
}
|
|
856
|
-
|
|
857
819
|
return statusByPath;
|
|
858
820
|
}
|
|
859
|
-
|
|
860
821
|
function parseGitBlamePorcelain(stdout) {
|
|
861
|
-
const lines = String(stdout ||
|
|
822
|
+
const lines = String(stdout || "").split("\n");
|
|
862
823
|
const blameLines = [];
|
|
863
824
|
let index = 0;
|
|
864
|
-
|
|
865
825
|
while (index < lines.length) {
|
|
866
826
|
const header = lines[index];
|
|
867
|
-
const match = /^(\^?[0-9a-f]{40}) (\d+) (\d+)(?: (\d+))?$/.exec(header ||
|
|
827
|
+
const match = /^(\^?[0-9a-f]{40}) (\d+) (\d+)(?: (\d+))?$/.exec(header || "");
|
|
868
828
|
if (!match) {
|
|
869
829
|
index += 1;
|
|
870
830
|
continue;
|
|
871
831
|
}
|
|
872
|
-
|
|
873
832
|
const entry = {
|
|
874
|
-
commit: match[1].replace(/^\^/,
|
|
833
|
+
commit: match[1].replace(/^\^/, ""),
|
|
875
834
|
originalLineNumber: Number(match[2]),
|
|
876
835
|
lineNumber: Number(match[3]),
|
|
877
|
-
author:
|
|
878
|
-
authorMail:
|
|
836
|
+
author: "",
|
|
837
|
+
authorMail: "",
|
|
879
838
|
authorTime: null,
|
|
880
|
-
authorTimeIso:
|
|
881
|
-
summary:
|
|
882
|
-
content:
|
|
839
|
+
authorTimeIso: "",
|
|
840
|
+
summary: "",
|
|
841
|
+
content: ""
|
|
883
842
|
};
|
|
884
843
|
index += 1;
|
|
885
|
-
|
|
886
844
|
while (index < lines.length) {
|
|
887
845
|
const line = lines[index];
|
|
888
846
|
index += 1;
|
|
889
|
-
if (line.startsWith(
|
|
847
|
+
if (line.startsWith(" ")) {
|
|
890
848
|
entry.content = line.slice(1);
|
|
891
849
|
break;
|
|
892
850
|
}
|
|
893
|
-
|
|
894
|
-
const separator = line.indexOf(' ');
|
|
851
|
+
const separator = line.indexOf(" ");
|
|
895
852
|
const fieldName = separator === -1 ? line : line.slice(0, separator);
|
|
896
|
-
const value = separator === -1 ?
|
|
897
|
-
if (fieldName ===
|
|
898
|
-
if (fieldName ===
|
|
899
|
-
if (fieldName ===
|
|
853
|
+
const value = separator === -1 ? "" : line.slice(separator + 1);
|
|
854
|
+
if (fieldName === "author") entry.author = value;
|
|
855
|
+
if (fieldName === "author-mail") entry.authorMail = value.replace(/^<|>$/g, "");
|
|
856
|
+
if (fieldName === "author-time") {
|
|
900
857
|
const time = Number(value);
|
|
901
858
|
entry.authorTime = Number.isFinite(time) ? time : null;
|
|
902
|
-
entry.authorTimeIso = Number.isFinite(time) ? new Date(time *
|
|
859
|
+
entry.authorTimeIso = Number.isFinite(time) ? new Date(time * 1e3).toISOString() : "";
|
|
903
860
|
}
|
|
904
|
-
if (fieldName ===
|
|
861
|
+
if (fieldName === "summary") entry.summary = value;
|
|
905
862
|
}
|
|
906
|
-
|
|
907
863
|
const uncommitted = /^0+$/.test(entry.commit);
|
|
908
864
|
blameLines.push({
|
|
909
865
|
...entry,
|
|
910
|
-
shortCommit: uncommitted ?
|
|
911
|
-
uncommitted
|
|
866
|
+
shortCommit: uncommitted ? "uncommitted" : entry.commit.slice(0, 8),
|
|
867
|
+
uncommitted
|
|
912
868
|
});
|
|
913
869
|
}
|
|
914
|
-
|
|
915
870
|
return blameLines;
|
|
916
871
|
}
|
|
917
|
-
|
|
918
872
|
function parseUnifiedDiffHunks(patch) {
|
|
919
|
-
const lines = String(patch ||
|
|
873
|
+
const lines = String(patch || "").split("\n");
|
|
920
874
|
const hunks = [];
|
|
921
875
|
let hunk = null;
|
|
922
|
-
|
|
923
876
|
const finishHunk = () => {
|
|
924
877
|
if (hunk) {
|
|
925
878
|
hunks.push({
|
|
926
879
|
...hunk,
|
|
927
|
-
patch: hunk.patchLines.join(
|
|
880
|
+
patch: hunk.patchLines.join("\n")
|
|
928
881
|
});
|
|
929
882
|
}
|
|
930
883
|
hunk = null;
|
|
931
884
|
};
|
|
932
|
-
|
|
933
885
|
for (const line of lines) {
|
|
934
886
|
const match = /^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@(.*)$/.exec(line);
|
|
935
887
|
if (match) {
|
|
@@ -940,21 +892,18 @@ function parseUnifiedDiffHunks(patch) {
|
|
|
940
892
|
oldLines: Number(match[2] || 1),
|
|
941
893
|
newStart: Number(match[3]),
|
|
942
894
|
newLines: Number(match[4] || 1),
|
|
943
|
-
heading: match[5] ? match[5].trim() :
|
|
944
|
-
patchLines: [line]
|
|
895
|
+
heading: match[5] ? match[5].trim() : "",
|
|
896
|
+
patchLines: [line]
|
|
945
897
|
};
|
|
946
898
|
continue;
|
|
947
899
|
}
|
|
948
|
-
|
|
949
900
|
if (hunk) {
|
|
950
901
|
hunk.patchLines.push(line);
|
|
951
902
|
}
|
|
952
903
|
}
|
|
953
|
-
|
|
954
904
|
finishHunk();
|
|
955
905
|
return hunks;
|
|
956
906
|
}
|
|
957
|
-
|
|
958
907
|
function intralineRanges(leftText, rightText) {
|
|
959
908
|
const changes = diffChars(leftText, rightText, { timeout: 100 });
|
|
960
909
|
if (!changes) return {};
|
|
@@ -978,28 +927,27 @@ function intralineRanges(leftText, rightText) {
|
|
|
978
927
|
rightOffset += length;
|
|
979
928
|
}
|
|
980
929
|
return {
|
|
981
|
-
...
|
|
982
|
-
...
|
|
930
|
+
...left.length ? { left } : {},
|
|
931
|
+
...right.length ? { right } : {}
|
|
983
932
|
};
|
|
984
933
|
}
|
|
985
|
-
|
|
986
934
|
function parseUnifiedDiffRows(patch) {
|
|
987
|
-
return parseUnifiedDiffHunks(patch).map(hunk => {
|
|
935
|
+
return parseUnifiedDiffHunks(patch).map((hunk) => {
|
|
988
936
|
let oldLine = hunk.oldStart;
|
|
989
937
|
let newLine = hunk.newStart;
|
|
990
938
|
const rows = [];
|
|
991
939
|
let previousCell = null;
|
|
992
940
|
for (const line of hunk.patchLines.slice(1)) {
|
|
993
|
-
if (line.startsWith(
|
|
941
|
+
if (line.startsWith("\")) {
|
|
994
942
|
if (previousCell) previousCell.missingNewlineAtEnd = true;
|
|
995
943
|
continue;
|
|
996
944
|
}
|
|
997
|
-
if (line.startsWith(
|
|
945
|
+
if (line.startsWith(" ")) {
|
|
998
946
|
const text = line.slice(1);
|
|
999
947
|
const row = {
|
|
1000
|
-
kind:
|
|
948
|
+
kind: "context",
|
|
1001
949
|
left: { line: oldLine, text },
|
|
1002
|
-
right: { line: newLine, text }
|
|
950
|
+
right: { line: newLine, text }
|
|
1003
951
|
};
|
|
1004
952
|
rows.push(row);
|
|
1005
953
|
previousCell = row.right;
|
|
@@ -1007,40 +955,41 @@ function parseUnifiedDiffRows(patch) {
|
|
|
1007
955
|
newLine++;
|
|
1008
956
|
continue;
|
|
1009
957
|
}
|
|
1010
|
-
if (line.startsWith(
|
|
1011
|
-
const row = { kind:
|
|
958
|
+
if (line.startsWith("-")) {
|
|
959
|
+
const row = { kind: "deleted", left: { line: oldLine, text: line.slice(1) } };
|
|
1012
960
|
rows.push(row);
|
|
1013
961
|
previousCell = row.left;
|
|
1014
962
|
oldLine++;
|
|
1015
963
|
continue;
|
|
1016
964
|
}
|
|
1017
|
-
if (line.startsWith(
|
|
1018
|
-
const row = { kind:
|
|
965
|
+
if (line.startsWith("+")) {
|
|
966
|
+
const row = { kind: "added", right: { line: newLine, text: line.slice(1) } };
|
|
1019
967
|
rows.push(row);
|
|
1020
968
|
previousCell = row.right;
|
|
1021
969
|
newLine++;
|
|
1022
970
|
}
|
|
1023
971
|
}
|
|
1024
972
|
const pairedRows = [];
|
|
1025
|
-
for (let index = 0; index < rows.length;) {
|
|
1026
|
-
if (rows[index].kind !==
|
|
973
|
+
for (let index = 0; index < rows.length; ) {
|
|
974
|
+
if (rows[index].kind !== "deleted") {
|
|
1027
975
|
pairedRows.push(rows[index]);
|
|
1028
976
|
index += 1;
|
|
1029
977
|
continue;
|
|
1030
978
|
}
|
|
1031
979
|
const deleted = [];
|
|
1032
|
-
while (index < rows.length && rows[index].kind ===
|
|
980
|
+
while (index < rows.length && rows[index].kind === "deleted") deleted.push(rows[index++]);
|
|
1033
981
|
const added = [];
|
|
1034
|
-
while (index < rows.length && rows[index].kind ===
|
|
982
|
+
while (index < rows.length && rows[index].kind === "added") added.push(rows[index++]);
|
|
1035
983
|
const paired = Math.min(deleted.length, added.length);
|
|
1036
984
|
for (let pairIndex = 0; pairIndex < paired; pairIndex += 1) {
|
|
1037
985
|
const left = deleted[pairIndex].left;
|
|
1038
986
|
const right = added[pairIndex].right;
|
|
987
|
+
if (!left || !right) continue;
|
|
1039
988
|
const intraline = intralineRanges(left.text, right.text);
|
|
1040
989
|
pairedRows.push({
|
|
1041
|
-
kind:
|
|
1042
|
-
left: { ...left, ...
|
|
1043
|
-
right: { ...right, ...
|
|
990
|
+
kind: "changed",
|
|
991
|
+
left: { ...left, ...intraline.left ? { intraline: intraline.left } : {} },
|
|
992
|
+
right: { ...right, ...intraline.right ? { intraline: intraline.right } : {} }
|
|
1044
993
|
});
|
|
1045
994
|
}
|
|
1046
995
|
pairedRows.push(...deleted.slice(paired));
|
|
@@ -1052,55 +1001,48 @@ function parseUnifiedDiffRows(patch) {
|
|
|
1052
1001
|
oldLines: hunk.oldLines,
|
|
1053
1002
|
newStart: hunk.newStart,
|
|
1054
1003
|
newLines: hunk.newLines,
|
|
1055
|
-
rows: pairedRows
|
|
1004
|
+
rows: pairedRows
|
|
1056
1005
|
};
|
|
1057
1006
|
});
|
|
1058
1007
|
}
|
|
1059
|
-
|
|
1060
1008
|
function lineInUnifiedDiffHunk(hunk, side, lineNumber) {
|
|
1061
|
-
const start = side ===
|
|
1062
|
-
const count = side ===
|
|
1009
|
+
const start = side === "old" ? hunk.oldStart : hunk.newStart;
|
|
1010
|
+
const count = side === "old" ? hunk.oldLines : hunk.newLines;
|
|
1063
1011
|
const end = Math.max(start, start + count - 1);
|
|
1064
1012
|
return lineNumber >= start && lineNumber <= end;
|
|
1065
1013
|
}
|
|
1066
|
-
|
|
1067
1014
|
function selectUnifiedDiffHunk(patch, side, lineNumber) {
|
|
1068
1015
|
const hunks = parseUnifiedDiffHunks(patch);
|
|
1069
|
-
return hunks.find(hunk => lineInUnifiedDiffHunk(hunk, side, lineNumber)) || null;
|
|
1016
|
+
return hunks.find((hunk) => lineInUnifiedDiffHunk(hunk, side, lineNumber)) || null;
|
|
1070
1017
|
}
|
|
1071
|
-
|
|
1072
1018
|
function createAddedFileLineChangesHunk(content, lineNumber, contextLines = 20) {
|
|
1073
|
-
const lines = String(content ||
|
|
1074
|
-
if (lines.length > 0 && lines[lines.length - 1] ===
|
|
1019
|
+
const lines = String(content || "").split("\n");
|
|
1020
|
+
if (lines.length > 0 && lines[lines.length - 1] === "") lines.pop();
|
|
1075
1021
|
if (lines.length === 0) return null;
|
|
1076
|
-
|
|
1077
1022
|
const startIndex = Math.max(0, lineNumber - contextLines - 1);
|
|
1078
1023
|
const endIndex = Math.min(lines.length, lineNumber + contextLines);
|
|
1079
1024
|
const newStart = startIndex + 1;
|
|
1080
1025
|
const newLines = endIndex - startIndex;
|
|
1081
1026
|
const patchLines = [
|
|
1082
1027
|
`@@ -0,0 +${newStart},${newLines} @@`,
|
|
1083
|
-
...lines.slice(startIndex, endIndex).map(line => `+${line}`)
|
|
1028
|
+
...lines.slice(startIndex, endIndex).map((line) => `+${line}`)
|
|
1084
1029
|
];
|
|
1085
|
-
|
|
1086
1030
|
return {
|
|
1087
1031
|
header: patchLines[0],
|
|
1088
1032
|
oldStart: 0,
|
|
1089
1033
|
oldLines: 0,
|
|
1090
1034
|
newStart,
|
|
1091
1035
|
newLines,
|
|
1092
|
-
heading:
|
|
1093
|
-
patch: patchLines.join(
|
|
1036
|
+
heading: "",
|
|
1037
|
+
patch: patchLines.join("\n")
|
|
1094
1038
|
};
|
|
1095
1039
|
}
|
|
1096
|
-
|
|
1097
1040
|
function strongestStatus(current, next) {
|
|
1098
1041
|
if (!current) return next;
|
|
1099
1042
|
return gitStatusRank(next.kind) > gitStatusRank(current.kind) ? next : current;
|
|
1100
1043
|
}
|
|
1101
|
-
|
|
1102
1044
|
function buildDescendantGitStatusByDirectory(statusByPath) {
|
|
1103
|
-
const statusByDirectory = new Map();
|
|
1045
|
+
const statusByDirectory = /* @__PURE__ */ new Map();
|
|
1104
1046
|
statusByPath.forEach((status, statusPath) => {
|
|
1105
1047
|
let directory = parentDirectory(statusPath);
|
|
1106
1048
|
while (directory) {
|
|
@@ -1110,8 +1052,36 @@ function buildDescendantGitStatusByDirectory(statusByPath) {
|
|
|
1110
1052
|
});
|
|
1111
1053
|
return statusByDirectory;
|
|
1112
1054
|
}
|
|
1113
|
-
|
|
1114
1055
|
class WorkspaceFileService {
|
|
1056
|
+
blameTimeoutMs;
|
|
1057
|
+
bundledRipgrepModule;
|
|
1058
|
+
bundledRipgrepModulePath;
|
|
1059
|
+
commandRunner;
|
|
1060
|
+
diffMaxBuffer;
|
|
1061
|
+
diffTimeoutMs;
|
|
1062
|
+
disposed;
|
|
1063
|
+
flushWorkspaceWrites;
|
|
1064
|
+
gitHistoryMaxBuffer;
|
|
1065
|
+
gitHistoryTimeoutMs;
|
|
1066
|
+
gitPath;
|
|
1067
|
+
gitStatusCache;
|
|
1068
|
+
gitStatusCacheTtlMs;
|
|
1069
|
+
gitStatusInlineTimeoutMs;
|
|
1070
|
+
gitStatusTimeoutMs;
|
|
1071
|
+
maxFileSize;
|
|
1072
|
+
maxPreviewFileSize;
|
|
1073
|
+
maxWriteSize;
|
|
1074
|
+
mutationQueues;
|
|
1075
|
+
ownsCommandRunner;
|
|
1076
|
+
rgFallbackPath;
|
|
1077
|
+
rgPath;
|
|
1078
|
+
searchLimit;
|
|
1079
|
+
searchTimeoutMs;
|
|
1080
|
+
watchDepth;
|
|
1081
|
+
watchOptions;
|
|
1082
|
+
watcherLifecycleGeneration;
|
|
1083
|
+
watcherSubscriptionGeneration;
|
|
1084
|
+
watchers;
|
|
1115
1085
|
constructor(options = {}) {
|
|
1116
1086
|
this.maxFileSize = options.maxFileSize || DEFAULT_MAX_FILE_SIZE;
|
|
1117
1087
|
this.maxWriteSize = options.maxWriteSize || DEFAULT_MAX_WRITE_SIZE;
|
|
@@ -1124,39 +1094,38 @@ class WorkspaceFileService {
|
|
|
1124
1094
|
this.gitHistoryTimeoutMs = options.gitHistoryTimeoutMs ?? DEFAULT_GIT_HISTORY_TIMEOUT_MS;
|
|
1125
1095
|
this.gitHistoryMaxBuffer = options.gitHistoryMaxBuffer ?? DEFAULT_GIT_HISTORY_MAX_BUFFER;
|
|
1126
1096
|
const bundledRipgrepPath = resolveBundledRipgrepPath();
|
|
1127
|
-
this.rgPath = options.rgPath || process.env.FARMING_RG_BIN || bundledRipgrepPath ||
|
|
1128
|
-
this.rgFallbackPath = options.rgFallbackPath ?? (this.rgPath ===
|
|
1097
|
+
this.rgPath = options.rgPath || process.env.FARMING_RG_BIN || bundledRipgrepPath || "rg";
|
|
1098
|
+
this.rgFallbackPath = options.rgFallbackPath ?? (this.rgPath === "rg" ? bundledRipgrepPath : "rg");
|
|
1129
1099
|
this.bundledRipgrepModulePath = options.bundledRipgrepModulePath ?? resolveBundledRipgrepModulePath();
|
|
1130
1100
|
this.bundledRipgrepModule = null;
|
|
1131
|
-
this.gitPath = options.gitPath ||
|
|
1101
|
+
this.gitPath = options.gitPath || "git";
|
|
1132
1102
|
this.gitStatusCacheTtlMs = options.gitStatusCacheTtlMs ?? DEFAULT_GIT_STATUS_CACHE_TTL_MS;
|
|
1133
1103
|
this.gitStatusInlineTimeoutMs = options.gitStatusInlineTimeoutMs ?? DEFAULT_GIT_STATUS_INLINE_TIMEOUT_MS;
|
|
1134
1104
|
this.gitStatusTimeoutMs = options.gitStatusTimeoutMs ?? DEFAULT_GIT_STATUS_TIMEOUT_MS;
|
|
1135
|
-
this.gitStatusCache = new Map();
|
|
1136
|
-
this.mutationQueues = new Map();
|
|
1105
|
+
this.gitStatusCache = /* @__PURE__ */ new Map();
|
|
1106
|
+
this.mutationQueues = /* @__PURE__ */ new Map();
|
|
1137
1107
|
this.flushWorkspaceWrites = options.flushWorkspaceWrites !== false;
|
|
1138
|
-
this.watchers = new Map();
|
|
1108
|
+
this.watchers = /* @__PURE__ */ new Map();
|
|
1139
1109
|
this.watcherSubscriptionGeneration = 0;
|
|
1140
1110
|
this.watcherLifecycleGeneration = 0;
|
|
1141
1111
|
this.disposed = false;
|
|
1142
1112
|
this.watchOptions = options.watchOptions || {};
|
|
1143
|
-
this.watchDepth = Number.isFinite(options.watchDepth) ? Math.max(0, options.watchDepth) : DEFAULT_WATCH_DEPTH;
|
|
1113
|
+
this.watchDepth = Number.isFinite(options.watchDepth) ? Math.max(0, options.watchDepth ?? 0) : DEFAULT_WATCH_DEPTH;
|
|
1144
1114
|
this.commandRunner = options.commandRunner || new CommandRunner(options.commandRunnerOptions);
|
|
1145
1115
|
this.ownsCommandRunner = !options.commandRunner;
|
|
1146
1116
|
}
|
|
1147
|
-
|
|
1148
1117
|
execFile(command, args, options = {}) {
|
|
1149
1118
|
return this.commandRunner.run(command, args, {
|
|
1150
1119
|
maxBuffer: 2 * 1024 * 1024,
|
|
1151
|
-
...options
|
|
1120
|
+
...options
|
|
1152
1121
|
});
|
|
1153
1122
|
}
|
|
1154
|
-
|
|
1155
1123
|
async runWorkspaceMutation(workspaceRoot, operation) {
|
|
1156
1124
|
const root = await this.resolveRoot(workspaceRoot);
|
|
1157
1125
|
const previous = this.mutationQueues.get(root) || Promise.resolve();
|
|
1158
|
-
let release
|
|
1159
|
-
|
|
1126
|
+
let release = () => {
|
|
1127
|
+
};
|
|
1128
|
+
const current = new Promise((resolve) => {
|
|
1160
1129
|
release = resolve;
|
|
1161
1130
|
});
|
|
1162
1131
|
const tail = previous.then(() => current);
|
|
@@ -1171,18 +1140,17 @@ class WorkspaceFileService {
|
|
|
1171
1140
|
}
|
|
1172
1141
|
}
|
|
1173
1142
|
}
|
|
1174
|
-
|
|
1175
1143
|
async waitForWorkspaceMutations(workspaceRoot) {
|
|
1176
1144
|
const root = await this.resolveRoot(workspaceRoot);
|
|
1177
1145
|
const pending = this.mutationQueues.get(root);
|
|
1178
1146
|
if (pending) await pending;
|
|
1179
1147
|
return root;
|
|
1180
1148
|
}
|
|
1181
|
-
|
|
1182
1149
|
async execRipgrep(args, options = {}) {
|
|
1183
1150
|
try {
|
|
1184
1151
|
return await this.execFile(this.rgPath, args, options);
|
|
1185
|
-
} catch (
|
|
1152
|
+
} catch (caught) {
|
|
1153
|
+
const error = processError(caught);
|
|
1186
1154
|
if (!isCommandUnavailable(error) || !this.rgFallbackPath || this.rgFallbackPath === this.rgPath) {
|
|
1187
1155
|
if (isCommandUnavailable(error)) {
|
|
1188
1156
|
return this.runBundledRipgrep(args, options);
|
|
@@ -1192,7 +1160,8 @@ class WorkspaceFileService {
|
|
|
1192
1160
|
this.rgPath = this.rgFallbackPath;
|
|
1193
1161
|
try {
|
|
1194
1162
|
return await this.execFile(this.rgPath, args, options);
|
|
1195
|
-
} catch (
|
|
1163
|
+
} catch (caughtFallback) {
|
|
1164
|
+
const fallbackError = processError(caughtFallback);
|
|
1196
1165
|
if (isCommandUnavailable(fallbackError)) {
|
|
1197
1166
|
return this.runBundledRipgrep(args, options);
|
|
1198
1167
|
}
|
|
@@ -1200,11 +1169,10 @@ class WorkspaceFileService {
|
|
|
1200
1169
|
}
|
|
1201
1170
|
}
|
|
1202
1171
|
}
|
|
1203
|
-
|
|
1204
1172
|
async loadBundledRipgrep() {
|
|
1205
1173
|
if (!this.bundledRipgrepModulePath) {
|
|
1206
|
-
const error = new Error(
|
|
1207
|
-
error.code =
|
|
1174
|
+
const error = new Error("bundled ripgrep is unavailable");
|
|
1175
|
+
error.code = "ENOENT";
|
|
1208
1176
|
throw error;
|
|
1209
1177
|
}
|
|
1210
1178
|
if (!this.bundledRipgrepModule) {
|
|
@@ -1212,29 +1180,26 @@ class WorkspaceFileService {
|
|
|
1212
1180
|
}
|
|
1213
1181
|
return this.bundledRipgrepModule;
|
|
1214
1182
|
}
|
|
1215
|
-
|
|
1216
1183
|
async runBundledRipgrep(args, options = {}) {
|
|
1217
1184
|
const { ripgrep } = await this.loadBundledRipgrep();
|
|
1218
1185
|
const cwd = options.cwd || process.cwd();
|
|
1219
1186
|
const run = ripgrep(args, {
|
|
1220
1187
|
buffer: true,
|
|
1221
1188
|
env: options.env || process.env,
|
|
1222
|
-
preopens: {
|
|
1189
|
+
preopens: { ".": cwd }
|
|
1223
1190
|
});
|
|
1224
|
-
const result = await (options.timeout
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
const stdout = result.stdout || '';
|
|
1237
|
-
const stderr = result.stderr || '';
|
|
1191
|
+
const result = await (options.timeout ? Promise.race([
|
|
1192
|
+
run,
|
|
1193
|
+
new Promise((_, reject) => {
|
|
1194
|
+
setTimeout(() => {
|
|
1195
|
+
const error = new Error("bundled ripgrep timed out");
|
|
1196
|
+
error.code = "ETIMEDOUT";
|
|
1197
|
+
reject(error);
|
|
1198
|
+
}, options.timeout);
|
|
1199
|
+
})
|
|
1200
|
+
]) : run);
|
|
1201
|
+
const stdout = result.stdout || "";
|
|
1202
|
+
const stderr = result.stderr || "";
|
|
1238
1203
|
if (result.code && result.code !== 0) {
|
|
1239
1204
|
const error = new Error(stderr || `ripgrep exited with code ${result.code}`);
|
|
1240
1205
|
error.code = result.code;
|
|
@@ -1244,44 +1209,41 @@ class WorkspaceFileService {
|
|
|
1244
1209
|
}
|
|
1245
1210
|
return { stdout, stderr };
|
|
1246
1211
|
}
|
|
1247
|
-
|
|
1248
1212
|
async listSearchFiles(root, searchPath, timeout) {
|
|
1249
1213
|
try {
|
|
1250
1214
|
return await this.execFile(this.gitPath, [
|
|
1251
|
-
|
|
1215
|
+
"-C",
|
|
1252
1216
|
root,
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
searchPath
|
|
1217
|
+
"ls-files",
|
|
1218
|
+
"-co",
|
|
1219
|
+
"--exclude-standard",
|
|
1220
|
+
"--",
|
|
1221
|
+
searchPath
|
|
1258
1222
|
], { cwd: root, timeout, maxBuffer: SEARCH_FILE_LIST_MAX_BUFFER });
|
|
1259
1223
|
} catch {
|
|
1260
1224
|
return this.execRipgrep([
|
|
1261
|
-
|
|
1262
|
-
|
|
1225
|
+
"--files",
|
|
1226
|
+
"--hidden",
|
|
1263
1227
|
...searchExcludeGlobArgs(),
|
|
1264
|
-
searchPath
|
|
1228
|
+
searchPath
|
|
1265
1229
|
], { cwd: root, timeout, maxBuffer: SEARCH_FILE_LIST_MAX_BUFFER });
|
|
1266
1230
|
}
|
|
1267
1231
|
}
|
|
1268
|
-
|
|
1269
1232
|
async collectGitIgnoredPathMatchCandidates(root, searchPath, query, limit, timeout) {
|
|
1270
1233
|
const candidateLimit = Math.max(PATH_SEARCH_MIN_CANDIDATES, limit * PATH_SEARCH_CANDIDATE_MULTIPLIER);
|
|
1271
1234
|
const { stdout } = await this.execFile(this.gitPath, [
|
|
1272
|
-
|
|
1235
|
+
"-C",
|
|
1273
1236
|
root,
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
...gitSearchExcludePathspecArgs(true, searchPath)
|
|
1237
|
+
"ls-files",
|
|
1238
|
+
"--others",
|
|
1239
|
+
"--ignored",
|
|
1240
|
+
"--exclude-standard",
|
|
1241
|
+
...gitSearchExcludePathspecArgs(true, searchPath)
|
|
1279
1242
|
], { cwd: root, timeout, maxBuffer: SEARCH_FILE_LIST_MAX_BUFFER });
|
|
1280
1243
|
const scoredMatches = [];
|
|
1281
|
-
const seenMatchPaths = new Set();
|
|
1244
|
+
const seenMatchPaths = /* @__PURE__ */ new Set();
|
|
1282
1245
|
let truncated = false;
|
|
1283
|
-
|
|
1284
|
-
for (const line of stdout.split('\n')) {
|
|
1246
|
+
for (const line of String(stdout).split("\n")) {
|
|
1285
1247
|
const filePath = normalizeSearchResultPath(line);
|
|
1286
1248
|
if (!filePath || isSearchIgnoredRelativePath(filePath, true)) continue;
|
|
1287
1249
|
for (const match of pathSearchMatchesForFile(filePath, query, true)) {
|
|
@@ -1295,44 +1257,38 @@ class WorkspaceFileService {
|
|
|
1295
1257
|
}
|
|
1296
1258
|
if (truncated) break;
|
|
1297
1259
|
}
|
|
1298
|
-
|
|
1299
1260
|
scoredMatches.sort(comparePathSearchMatches);
|
|
1300
1261
|
return {
|
|
1301
|
-
matches: scoredMatches.slice(0, limit).map(match => ({
|
|
1302
|
-
kind:
|
|
1262
|
+
matches: scoredMatches.slice(0, limit).map((match) => ({
|
|
1263
|
+
kind: "path",
|
|
1303
1264
|
entryType: match.entryType,
|
|
1304
1265
|
path: match.path,
|
|
1305
1266
|
lineNumber: 1,
|
|
1306
|
-
lines:
|
|
1307
|
-
ranges: []
|
|
1267
|
+
lines: "",
|
|
1268
|
+
ranges: []
|
|
1308
1269
|
})),
|
|
1309
|
-
truncated
|
|
1270
|
+
truncated
|
|
1310
1271
|
};
|
|
1311
1272
|
}
|
|
1312
|
-
|
|
1313
1273
|
streamPathMatches(command, root, searchPath, query, limit, timeout, stopAtLimit = false) {
|
|
1314
1274
|
const args = [
|
|
1315
|
-
|
|
1316
|
-
|
|
1275
|
+
"--files",
|
|
1276
|
+
"--hidden",
|
|
1317
1277
|
...searchExcludeGlobArgs(),
|
|
1318
|
-
searchPath
|
|
1278
|
+
searchPath
|
|
1319
1279
|
];
|
|
1320
|
-
const candidateLimit = stopAtLimit
|
|
1321
|
-
? limit
|
|
1322
|
-
: Math.max(PATH_SEARCH_MIN_CANDIDATES, limit * PATH_SEARCH_CANDIDATE_MULTIPLIER);
|
|
1323
|
-
|
|
1280
|
+
const candidateLimit = stopAtLimit ? limit : Math.max(PATH_SEARCH_MIN_CANDIDATES, limit * PATH_SEARCH_CANDIDATE_MULTIPLIER);
|
|
1324
1281
|
return new Promise((resolve, reject) => {
|
|
1325
1282
|
const child = spawn(command, args, {
|
|
1326
1283
|
cwd: root,
|
|
1327
|
-
stdio: [
|
|
1284
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
1328
1285
|
});
|
|
1329
|
-
let pending =
|
|
1330
|
-
let stderr =
|
|
1286
|
+
let pending = "";
|
|
1287
|
+
let stderr = "";
|
|
1331
1288
|
let settled = false;
|
|
1332
1289
|
let truncated = false;
|
|
1333
1290
|
const scoredMatches = [];
|
|
1334
|
-
const seenMatchPaths = new Set();
|
|
1335
|
-
|
|
1291
|
+
const seenMatchPaths = /* @__PURE__ */ new Set();
|
|
1336
1292
|
const settle = (error) => {
|
|
1337
1293
|
if (settled) return;
|
|
1338
1294
|
settled = true;
|
|
@@ -1343,24 +1299,22 @@ class WorkspaceFileService {
|
|
|
1343
1299
|
}
|
|
1344
1300
|
scoredMatches.sort(comparePathSearchMatches);
|
|
1345
1301
|
resolve({
|
|
1346
|
-
matches: scoredMatches.slice(0, limit).map(match => ({
|
|
1347
|
-
kind:
|
|
1302
|
+
matches: scoredMatches.slice(0, limit).map((match) => ({
|
|
1303
|
+
kind: "path",
|
|
1348
1304
|
entryType: match.entryType,
|
|
1349
1305
|
path: match.path,
|
|
1350
1306
|
lineNumber: 1,
|
|
1351
|
-
lines:
|
|
1352
|
-
ranges: []
|
|
1307
|
+
lines: "",
|
|
1308
|
+
ranges: []
|
|
1353
1309
|
})),
|
|
1354
|
-
truncated
|
|
1310
|
+
truncated
|
|
1355
1311
|
});
|
|
1356
1312
|
};
|
|
1357
|
-
|
|
1358
1313
|
const stopEarly = () => {
|
|
1359
1314
|
truncated = true;
|
|
1360
1315
|
child.kill();
|
|
1361
1316
|
settle();
|
|
1362
1317
|
};
|
|
1363
|
-
|
|
1364
1318
|
const processLine = (line) => {
|
|
1365
1319
|
const filePath = normalizeSearchResultPath(line);
|
|
1366
1320
|
if (!filePath || isSearchIgnoredRelativePath(filePath)) return;
|
|
@@ -1374,38 +1328,33 @@ class WorkspaceFileService {
|
|
|
1374
1328
|
}
|
|
1375
1329
|
}
|
|
1376
1330
|
};
|
|
1377
|
-
|
|
1378
1331
|
const timer = setTimeout(() => {
|
|
1379
1332
|
truncated = true;
|
|
1380
1333
|
child.kill();
|
|
1381
1334
|
settle();
|
|
1382
1335
|
}, timeout);
|
|
1383
|
-
|
|
1384
|
-
child.stdout.on('data', chunk => {
|
|
1336
|
+
child.stdout.on("data", (chunk) => {
|
|
1385
1337
|
if (settled) return;
|
|
1386
|
-
pending += String(chunk ||
|
|
1387
|
-
const lines = pending.split(
|
|
1388
|
-
pending = lines.pop() ||
|
|
1338
|
+
pending += String(chunk || "");
|
|
1339
|
+
const lines = pending.split("\n");
|
|
1340
|
+
pending = lines.pop() || "";
|
|
1389
1341
|
for (const line of lines) {
|
|
1390
1342
|
processLine(line);
|
|
1391
1343
|
if (settled) return;
|
|
1392
1344
|
}
|
|
1393
1345
|
});
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
stderr += String(chunk || '');
|
|
1346
|
+
child.stderr.on("data", (chunk) => {
|
|
1347
|
+
stderr += String(chunk || "");
|
|
1397
1348
|
});
|
|
1398
|
-
|
|
1399
|
-
child.on('error', error => {
|
|
1349
|
+
child.on("error", (error) => {
|
|
1400
1350
|
settle(error);
|
|
1401
1351
|
});
|
|
1402
|
-
|
|
1403
|
-
child.on('close', (code, signal) => {
|
|
1352
|
+
child.on("close", (code, signal) => {
|
|
1404
1353
|
if (settled) return;
|
|
1405
1354
|
if (pending) processLine(pending);
|
|
1406
1355
|
if (settled) return;
|
|
1407
|
-
if (code && code !== 0 && signal !==
|
|
1408
|
-
const error = new Error(stderr ||
|
|
1356
|
+
if (code && code !== 0 && signal !== "SIGTERM") {
|
|
1357
|
+
const error = new Error(stderr || "path search failed");
|
|
1409
1358
|
error.code = code;
|
|
1410
1359
|
error.signal = signal;
|
|
1411
1360
|
error.stderr = stderr;
|
|
@@ -1416,11 +1365,11 @@ class WorkspaceFileService {
|
|
|
1416
1365
|
});
|
|
1417
1366
|
});
|
|
1418
1367
|
}
|
|
1419
|
-
|
|
1420
1368
|
async collectPathMatchCandidates(root, searchPath, query, limit, timeout, stopAtLimit = false) {
|
|
1421
1369
|
try {
|
|
1422
1370
|
return await this.streamPathMatches(this.rgPath, root, searchPath, query, limit, timeout, stopAtLimit);
|
|
1423
|
-
} catch (
|
|
1371
|
+
} catch (caught) {
|
|
1372
|
+
const error = processError(caught);
|
|
1424
1373
|
if (!isCommandUnavailable(error) || !this.rgFallbackPath || this.rgFallbackPath === this.rgPath) {
|
|
1425
1374
|
if (isCommandUnavailable(error)) {
|
|
1426
1375
|
return this.collectBundledPathMatchCandidates(root, searchPath, query, limit, timeout, stopAtLimit);
|
|
@@ -1430,7 +1379,8 @@ class WorkspaceFileService {
|
|
|
1430
1379
|
this.rgPath = this.rgFallbackPath;
|
|
1431
1380
|
try {
|
|
1432
1381
|
return await this.streamPathMatches(this.rgPath, root, searchPath, query, limit, timeout, stopAtLimit);
|
|
1433
|
-
} catch (
|
|
1382
|
+
} catch (caughtFallback) {
|
|
1383
|
+
const fallbackError = processError(caughtFallback);
|
|
1434
1384
|
if (isCommandUnavailable(fallbackError)) {
|
|
1435
1385
|
return this.collectBundledPathMatchCandidates(root, searchPath, query, limit, timeout, stopAtLimit);
|
|
1436
1386
|
}
|
|
@@ -1438,19 +1388,17 @@ class WorkspaceFileService {
|
|
|
1438
1388
|
}
|
|
1439
1389
|
}
|
|
1440
1390
|
}
|
|
1441
|
-
|
|
1442
1391
|
async collectDirectoryNameMatchCandidates(root, searchPath, query, limit, timeout) {
|
|
1443
1392
|
const startedAt = Date.now();
|
|
1444
1393
|
const allowPathMatch = isLikelyPathQuery(query);
|
|
1445
1394
|
const candidateLimit = Math.max(PATH_SEARCH_MIN_CANDIDATES, limit * PATH_SEARCH_CANDIDATE_MULTIPLIER);
|
|
1446
|
-
const startRelativePath = searchPath ===
|
|
1447
|
-
const startDirectory = path.resolve(root, startRelativePath ||
|
|
1395
|
+
const startRelativePath = searchPath === "." ? "" : normalizeSearchResultPath(searchPath);
|
|
1396
|
+
const startDirectory = path.resolve(root, startRelativePath || ".");
|
|
1448
1397
|
const queue = [{ target: startDirectory, relativePath: startRelativePath }];
|
|
1449
1398
|
const scoredMatches = [];
|
|
1450
|
-
const seenMatchPaths = new Set();
|
|
1399
|
+
const seenMatchPaths = /* @__PURE__ */ new Set();
|
|
1451
1400
|
let visited = 0;
|
|
1452
1401
|
let truncated = false;
|
|
1453
|
-
|
|
1454
1402
|
while (queue.length > 0) {
|
|
1455
1403
|
if (Date.now() - startedAt > timeout) {
|
|
1456
1404
|
truncated = true;
|
|
@@ -1460,18 +1408,15 @@ class WorkspaceFileService {
|
|
|
1460
1408
|
truncated = true;
|
|
1461
1409
|
break;
|
|
1462
1410
|
}
|
|
1463
|
-
|
|
1464
1411
|
const current = queue.shift();
|
|
1465
1412
|
if (!current || shouldPruneDirectoryNameSearch(current.relativePath)) continue;
|
|
1466
1413
|
visited += 1;
|
|
1467
|
-
|
|
1468
1414
|
let entries;
|
|
1469
1415
|
try {
|
|
1470
1416
|
entries = await fsp.readdir(current.target, { withFileTypes: true });
|
|
1471
1417
|
} catch {
|
|
1472
1418
|
continue;
|
|
1473
1419
|
}
|
|
1474
|
-
|
|
1475
1420
|
for (const entry of entries) {
|
|
1476
1421
|
if (!entry.isDirectory()) continue;
|
|
1477
1422
|
const childRelativePath = normalizeSearchResultPath(
|
|
@@ -1492,35 +1437,31 @@ class WorkspaceFileService {
|
|
|
1492
1437
|
}
|
|
1493
1438
|
if (truncated) break;
|
|
1494
1439
|
}
|
|
1495
|
-
|
|
1496
1440
|
scoredMatches.sort(comparePathSearchMatches);
|
|
1497
1441
|
return {
|
|
1498
|
-
matches: scoredMatches.slice(0, limit).map(match => ({
|
|
1499
|
-
kind:
|
|
1500
|
-
entryType:
|
|
1442
|
+
matches: scoredMatches.slice(0, limit).map((match) => ({
|
|
1443
|
+
kind: "path",
|
|
1444
|
+
entryType: "directory",
|
|
1501
1445
|
path: match.path,
|
|
1502
1446
|
lineNumber: 1,
|
|
1503
|
-
lines:
|
|
1504
|
-
ranges: []
|
|
1447
|
+
lines: "",
|
|
1448
|
+
ranges: []
|
|
1505
1449
|
})),
|
|
1506
|
-
truncated
|
|
1450
|
+
truncated
|
|
1507
1451
|
};
|
|
1508
1452
|
}
|
|
1509
|
-
|
|
1510
1453
|
async collectBundledPathMatchCandidates(root, searchPath, query, limit, timeout, stopAtLimit = false) {
|
|
1511
|
-
const candidateLimit = stopAtLimit
|
|
1512
|
-
? limit
|
|
1513
|
-
: Math.max(PATH_SEARCH_MIN_CANDIDATES, limit * PATH_SEARCH_CANDIDATE_MULTIPLIER);
|
|
1454
|
+
const candidateLimit = stopAtLimit ? limit : Math.max(PATH_SEARCH_MIN_CANDIDATES, limit * PATH_SEARCH_CANDIDATE_MULTIPLIER);
|
|
1514
1455
|
const { stdout } = await this.runBundledRipgrep([
|
|
1515
|
-
|
|
1516
|
-
|
|
1456
|
+
"--files",
|
|
1457
|
+
"--hidden",
|
|
1517
1458
|
...searchExcludeGlobArgs(),
|
|
1518
|
-
searchPath
|
|
1459
|
+
searchPath
|
|
1519
1460
|
], { cwd: root, timeout, maxBuffer: SEARCH_FILE_LIST_MAX_BUFFER });
|
|
1520
1461
|
const scoredMatches = [];
|
|
1521
|
-
const seenMatchPaths = new Set();
|
|
1462
|
+
const seenMatchPaths = /* @__PURE__ */ new Set();
|
|
1522
1463
|
let truncated = false;
|
|
1523
|
-
for (const line of stdout.split(
|
|
1464
|
+
for (const line of String(stdout).split("\n")) {
|
|
1524
1465
|
const filePath = normalizeSearchResultPath(line);
|
|
1525
1466
|
if (!filePath || isSearchIgnoredRelativePath(filePath)) continue;
|
|
1526
1467
|
for (const match of pathSearchMatchesForFile(filePath, query)) {
|
|
@@ -1536,310 +1477,284 @@ class WorkspaceFileService {
|
|
|
1536
1477
|
}
|
|
1537
1478
|
scoredMatches.sort(comparePathSearchMatches);
|
|
1538
1479
|
return {
|
|
1539
|
-
matches: scoredMatches.slice(0, limit).map(match => ({
|
|
1540
|
-
kind:
|
|
1480
|
+
matches: scoredMatches.slice(0, limit).map((match) => ({
|
|
1481
|
+
kind: "path",
|
|
1541
1482
|
entryType: match.entryType,
|
|
1542
1483
|
path: match.path,
|
|
1543
1484
|
lineNumber: 1,
|
|
1544
|
-
lines:
|
|
1545
|
-
ranges: []
|
|
1485
|
+
lines: "",
|
|
1486
|
+
ranges: []
|
|
1546
1487
|
})),
|
|
1547
|
-
truncated
|
|
1488
|
+
truncated
|
|
1548
1489
|
};
|
|
1549
1490
|
}
|
|
1550
|
-
|
|
1551
1491
|
async directPathMatchCandidate(root, query, includeIgnored = false) {
|
|
1552
1492
|
if (!isLikelyPathQuery(query)) return null;
|
|
1553
|
-
const candidatePath = normalizeSearchResultPath(String(query ||
|
|
1554
|
-
if (!candidatePath || candidatePath.includes(
|
|
1493
|
+
const candidatePath = normalizeSearchResultPath(String(query || "").trim().replace(/^\.\/+/, ""));
|
|
1494
|
+
if (!candidatePath || candidatePath.includes("\0") || isSearchIgnoredRelativePath(candidatePath, includeIgnored)) return null;
|
|
1555
1495
|
try {
|
|
1556
1496
|
const { target, relativePath } = await this.resolvePath(root, candidatePath);
|
|
1557
1497
|
if (!relativePath || isSearchIgnoredRelativePath(relativePath, includeIgnored)) return null;
|
|
1558
1498
|
const stat = await fsp.stat(target);
|
|
1559
1499
|
if (!stat.isFile()) return null;
|
|
1560
1500
|
return {
|
|
1561
|
-
kind:
|
|
1562
|
-
entryType:
|
|
1501
|
+
kind: "path",
|
|
1502
|
+
entryType: "file",
|
|
1563
1503
|
path: relativePath,
|
|
1564
1504
|
lineNumber: 1,
|
|
1565
|
-
lines:
|
|
1566
|
-
ranges: []
|
|
1505
|
+
lines: "",
|
|
1506
|
+
ranges: []
|
|
1567
1507
|
};
|
|
1568
1508
|
} catch {
|
|
1569
1509
|
return null;
|
|
1570
1510
|
}
|
|
1571
1511
|
}
|
|
1572
|
-
|
|
1573
1512
|
async resolveRoot(workspaceRoot) {
|
|
1574
|
-
if (typeof workspaceRoot !==
|
|
1575
|
-
throw new WorkspaceFileError(
|
|
1513
|
+
if (typeof workspaceRoot !== "string" || !workspaceRoot.trim()) {
|
|
1514
|
+
throw new WorkspaceFileError("workspace root is required", 400);
|
|
1576
1515
|
}
|
|
1577
|
-
|
|
1578
1516
|
let stat;
|
|
1579
1517
|
try {
|
|
1580
1518
|
stat = await fsp.stat(workspaceRoot);
|
|
1581
1519
|
} catch {
|
|
1582
|
-
throw new WorkspaceFileError(
|
|
1520
|
+
throw new WorkspaceFileError("workspace not found", 404);
|
|
1583
1521
|
}
|
|
1584
|
-
|
|
1585
1522
|
if (!stat.isDirectory()) {
|
|
1586
|
-
throw new WorkspaceFileError(
|
|
1523
|
+
throw new WorkspaceFileError("workspace must be a directory", 400);
|
|
1587
1524
|
}
|
|
1588
|
-
|
|
1589
1525
|
return fsp.realpath(workspaceRoot);
|
|
1590
1526
|
}
|
|
1591
|
-
|
|
1592
|
-
async resolvePath(workspaceRoot, userPath = '', options = {}) {
|
|
1527
|
+
async resolvePath(workspaceRoot, userPath = "", options = {}) {
|
|
1593
1528
|
const root = await this.resolveRoot(workspaceRoot);
|
|
1594
1529
|
const normalized = normalizeUserPath(userPath);
|
|
1595
1530
|
const target = path.resolve(root, normalized);
|
|
1596
1531
|
const requestedRelativePath = relativeFromRoot(root, target);
|
|
1597
|
-
|
|
1598
1532
|
if (!isInside(root, target)) {
|
|
1599
|
-
throw new WorkspaceFileError(
|
|
1533
|
+
throw new WorkspaceFileError("path must stay inside the workspace", 403);
|
|
1600
1534
|
}
|
|
1601
|
-
|
|
1602
1535
|
if (options.allowMissing) {
|
|
1603
1536
|
const parent = await fsp.realpath(path.dirname(target)).catch(() => null);
|
|
1604
1537
|
if (!parent || !isInside(root, parent)) {
|
|
1605
|
-
throw new WorkspaceFileError(
|
|
1538
|
+
throw new WorkspaceFileError("parent path must stay inside the workspace", 403);
|
|
1606
1539
|
}
|
|
1607
1540
|
return {
|
|
1608
1541
|
root,
|
|
1609
1542
|
target,
|
|
1610
1543
|
relativePath: requestedRelativePath,
|
|
1611
1544
|
actualRelativePath: requestedRelativePath,
|
|
1545
|
+
external: false,
|
|
1546
|
+
readOnly: false,
|
|
1547
|
+
symbolicLink: false
|
|
1612
1548
|
};
|
|
1613
1549
|
}
|
|
1614
|
-
|
|
1615
1550
|
let realTarget;
|
|
1616
1551
|
try {
|
|
1617
1552
|
realTarget = await fsp.realpath(target);
|
|
1618
1553
|
} catch {
|
|
1619
|
-
throw new WorkspaceFileError(
|
|
1554
|
+
throw new WorkspaceFileError("path not found", 404);
|
|
1620
1555
|
}
|
|
1621
|
-
|
|
1622
1556
|
const external = !isInside(root, realTarget);
|
|
1623
1557
|
if (external && !isInsideAnyRoot(options.allowedExternalRoots, realTarget)) {
|
|
1624
|
-
throw new WorkspaceFileError(
|
|
1558
|
+
throw new WorkspaceFileError("symlink target is outside allowed workspaces", 403);
|
|
1625
1559
|
}
|
|
1626
|
-
|
|
1627
1560
|
const requestedStat = await fsp.lstat(target).catch(() => null);
|
|
1628
|
-
|
|
1629
1561
|
return {
|
|
1630
1562
|
root,
|
|
1631
1563
|
target: realTarget,
|
|
1632
1564
|
relativePath: requestedRelativePath,
|
|
1633
|
-
actualRelativePath: external ?
|
|
1565
|
+
actualRelativePath: external ? "" : relativeFromRoot(root, realTarget),
|
|
1634
1566
|
external,
|
|
1635
1567
|
readOnly: external,
|
|
1636
|
-
symbolicLink: Boolean(requestedStat && requestedStat.isSymbolicLink())
|
|
1568
|
+
symbolicLink: Boolean(requestedStat && requestedStat.isSymbolicLink())
|
|
1637
1569
|
};
|
|
1638
1570
|
}
|
|
1639
|
-
|
|
1640
1571
|
async resolveEntryPath(workspaceRoot, userPath) {
|
|
1641
1572
|
const root = await this.resolveRoot(workspaceRoot);
|
|
1642
1573
|
const normalized = normalizeUserPath(userPath);
|
|
1643
1574
|
const target = path.resolve(root, normalized);
|
|
1644
1575
|
const relativePath = relativeFromRoot(root, target);
|
|
1645
1576
|
if (!relativePath) {
|
|
1646
|
-
throw new WorkspaceFileError(
|
|
1577
|
+
throw new WorkspaceFileError("workspace root cannot be changed", 400);
|
|
1647
1578
|
}
|
|
1648
1579
|
if (!isInside(root, target)) {
|
|
1649
|
-
throw new WorkspaceFileError(
|
|
1580
|
+
throw new WorkspaceFileError("path must stay inside the workspace", 403);
|
|
1650
1581
|
}
|
|
1651
1582
|
try {
|
|
1652
1583
|
await fsp.lstat(target);
|
|
1653
1584
|
} catch {
|
|
1654
|
-
throw new WorkspaceFileError(
|
|
1585
|
+
throw new WorkspaceFileError("path not found", 404);
|
|
1655
1586
|
}
|
|
1656
1587
|
const realParent = await fsp.realpath(path.dirname(target)).catch(() => null);
|
|
1657
1588
|
if (!realParent || !isInside(root, realParent)) {
|
|
1658
|
-
throw new WorkspaceFileError(
|
|
1589
|
+
throw new WorkspaceFileError("symlink target is read-only", 403);
|
|
1659
1590
|
}
|
|
1660
1591
|
return { root, target, relativePath };
|
|
1661
1592
|
}
|
|
1662
|
-
|
|
1663
|
-
async listTree(workspaceRoot, userPath = '', options = {}) {
|
|
1593
|
+
async listTree(workspaceRoot, userPath = "", options = {}) {
|
|
1664
1594
|
const root = await this.waitForWorkspaceMutations(workspaceRoot);
|
|
1665
1595
|
const { target, relativePath, external: parentExternal = false } = await this.resolvePath(root, userPath, options);
|
|
1666
1596
|
const stat = await fsp.stat(target);
|
|
1667
1597
|
if (!stat.isDirectory()) {
|
|
1668
|
-
throw new WorkspaceFileError(
|
|
1598
|
+
throw new WorkspaceFileError("path must be a directory", 400);
|
|
1669
1599
|
}
|
|
1670
|
-
|
|
1671
1600
|
const entries = await fsp.readdir(target, { withFileTypes: true });
|
|
1672
|
-
const visibleEntries = entries.filter(entry => !TREE_HIDDEN_NAMES.has(entry.name));
|
|
1601
|
+
const visibleEntries = entries.filter((entry) => !TREE_HIDDEN_NAMES.has(entry.name));
|
|
1673
1602
|
const [{ value: gitStatusByPath, pending: gitStatusPending }, ignoredPaths] = await Promise.all([
|
|
1674
1603
|
this.getGitStatusForTree(root),
|
|
1675
|
-
this.loadGitIgnoredPaths(root, visibleEntries.map(entry => joinRelativePath(relativePath, entry.name)))
|
|
1604
|
+
this.loadGitIgnoredPaths(root, visibleEntries.map((entry) => joinRelativePath(relativePath, entry.name)))
|
|
1676
1605
|
]);
|
|
1677
1606
|
const descendantGitStatusByPath = buildDescendantGitStatusByDirectory(gitStatusByPath);
|
|
1678
|
-
const items = await Promise.all(visibleEntries
|
|
1679
|
-
.
|
|
1680
|
-
|
|
1681
|
-
|
|
1607
|
+
const items = await Promise.all(visibleEntries.map(async (entry) => {
|
|
1608
|
+
const absolute = path.join(target, entry.name);
|
|
1609
|
+
let entryStat;
|
|
1610
|
+
try {
|
|
1611
|
+
entryStat = await fsp.lstat(absolute);
|
|
1612
|
+
} catch (caught) {
|
|
1613
|
+
const error = processError(caught);
|
|
1614
|
+
if (error && error.code === "ENOENT") return null;
|
|
1615
|
+
throw error;
|
|
1616
|
+
}
|
|
1617
|
+
const itemPath = joinRelativePath(relativePath, entry.name);
|
|
1618
|
+
let type = entryStat.isDirectory() ? "directory" : entryStat.isFile() ? "file" : "other";
|
|
1619
|
+
let symbolicLink = false;
|
|
1620
|
+
let external = parentExternal;
|
|
1621
|
+
let readOnly = parentExternal;
|
|
1622
|
+
let linkTarget = "";
|
|
1623
|
+
let linkError = "";
|
|
1624
|
+
let targetStat = entryStat;
|
|
1625
|
+
if (entryStat.isSymbolicLink()) {
|
|
1626
|
+
symbolicLink = true;
|
|
1682
1627
|
try {
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
if (
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
let readOnly = parentExternal;
|
|
1693
|
-
let linkTarget = '';
|
|
1694
|
-
let linkError = '';
|
|
1695
|
-
let targetStat = entryStat;
|
|
1696
|
-
if (entryStat.isSymbolicLink()) {
|
|
1697
|
-
symbolicLink = true;
|
|
1698
|
-
try {
|
|
1699
|
-
linkTarget = await fsp.realpath(absolute);
|
|
1700
|
-
external = !isInside(root, linkTarget);
|
|
1701
|
-
if (external && !isInsideAnyRoot(options.allowedExternalRoots, linkTarget)) {
|
|
1702
|
-
type = 'symlink';
|
|
1703
|
-
linkError = 'outside-allowed-roots';
|
|
1704
|
-
} else {
|
|
1705
|
-
targetStat = await fsp.stat(linkTarget);
|
|
1706
|
-
type = targetStat.isDirectory() ? 'directory' : targetStat.isFile() ? 'file' : 'other';
|
|
1707
|
-
readOnly = external;
|
|
1708
|
-
}
|
|
1709
|
-
} catch (error) {
|
|
1710
|
-
type = 'symlink';
|
|
1711
|
-
linkError = error && error.code === 'ENOENT' ? 'broken' : 'unavailable';
|
|
1628
|
+
linkTarget = await fsp.realpath(absolute);
|
|
1629
|
+
external = !isInside(root, linkTarget);
|
|
1630
|
+
if (external && !isInsideAnyRoot(options.allowedExternalRoots, linkTarget)) {
|
|
1631
|
+
type = "symlink";
|
|
1632
|
+
linkError = "outside-allowed-roots";
|
|
1633
|
+
} else {
|
|
1634
|
+
targetStat = await fsp.stat(linkTarget);
|
|
1635
|
+
type = targetStat.isDirectory() ? "directory" : targetStat.isFile() ? "file" : "other";
|
|
1636
|
+
readOnly = external;
|
|
1712
1637
|
}
|
|
1638
|
+
} catch (caught) {
|
|
1639
|
+
const error = processError(caught);
|
|
1640
|
+
type = "symlink";
|
|
1641
|
+
linkError = error && error.code === "ENOENT" ? "broken" : "unavailable";
|
|
1713
1642
|
}
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1643
|
+
}
|
|
1644
|
+
const directGitStatus = gitStatusByPath.get(itemPath);
|
|
1645
|
+
let descendantGitStatus = null;
|
|
1646
|
+
if (type === "directory" && !external) {
|
|
1647
|
+
descendantGitStatus = descendantGitStatusByPath.get(itemPath) || null;
|
|
1648
|
+
}
|
|
1649
|
+
return {
|
|
1650
|
+
name: entry.name,
|
|
1651
|
+
path: itemPath,
|
|
1652
|
+
type,
|
|
1653
|
+
size: targetStat.size,
|
|
1654
|
+
mtimeMs: targetStat.mtimeMs,
|
|
1655
|
+
version: workspaceEntryVersion(entryStat),
|
|
1656
|
+
...symbolicLink ? { symbolicLink: true } : {},
|
|
1657
|
+
...external ? { external: true } : {},
|
|
1658
|
+
...readOnly ? { readOnly: true } : {},
|
|
1659
|
+
...linkTarget ? { linkTarget } : {},
|
|
1660
|
+
...linkError ? { linkError } : {},
|
|
1661
|
+
...ignoredPaths.has(itemPath) ? { ignored: true } : {},
|
|
1662
|
+
...directGitStatus ? {
|
|
1663
|
+
gitStatus: directGitStatus.kind,
|
|
1664
|
+
gitStatusLabel: directGitStatus.label
|
|
1665
|
+
} : {},
|
|
1666
|
+
...descendantGitStatus ? {
|
|
1667
|
+
descendantGitStatus: descendantGitStatus.kind
|
|
1668
|
+
} : {}
|
|
1669
|
+
};
|
|
1670
|
+
}));
|
|
1742
1671
|
const visibleItems = items.filter(Boolean);
|
|
1743
|
-
|
|
1744
1672
|
visibleItems.sort((a, b) => {
|
|
1745
|
-
if (a.type ===
|
|
1746
|
-
if (a.type !==
|
|
1673
|
+
if (a.type === "directory" && b.type !== "directory") return -1;
|
|
1674
|
+
if (a.type !== "directory" && b.type === "directory") return 1;
|
|
1747
1675
|
return a.name.localeCompare(b.name);
|
|
1748
1676
|
});
|
|
1749
|
-
|
|
1750
1677
|
return {
|
|
1751
1678
|
path: relativePath,
|
|
1752
1679
|
items: visibleItems,
|
|
1753
|
-
gitStatusPending
|
|
1680
|
+
gitStatusPending
|
|
1754
1681
|
};
|
|
1755
1682
|
}
|
|
1756
|
-
|
|
1757
1683
|
invalidateGitStatus(root) {
|
|
1758
1684
|
if (root) this.gitStatusCache.delete(root);
|
|
1759
1685
|
}
|
|
1760
|
-
|
|
1761
1686
|
async loadGitStatusByPath(root, options = {}) {
|
|
1762
|
-
const untrackedFiles = options.untrackedFiles ||
|
|
1687
|
+
const untrackedFiles = options.untrackedFiles || "normal";
|
|
1763
1688
|
try {
|
|
1764
1689
|
const { stdout } = await this.execFile(this.gitPath, [
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1690
|
+
"status",
|
|
1691
|
+
"--porcelain=v1",
|
|
1692
|
+
"-z",
|
|
1768
1693
|
`--untracked-files=${untrackedFiles}`,
|
|
1769
|
-
|
|
1770
|
-
...gitStatusExcludePathspecArgs()
|
|
1771
|
-
], { cwd: root, timeout: options.timeoutMs
|
|
1694
|
+
"--ignored=no",
|
|
1695
|
+
...gitStatusExcludePathspecArgs()
|
|
1696
|
+
], { cwd: root, timeout: Number(options.timeoutMs) || this.gitStatusTimeoutMs });
|
|
1772
1697
|
return parseGitStatus(stdout);
|
|
1773
|
-
} catch (
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
const
|
|
1777
|
-
const
|
|
1778
|
-
|
|
1779
|
-
: '');
|
|
1698
|
+
} catch (caught) {
|
|
1699
|
+
const error = processError(caught);
|
|
1700
|
+
if (error.code === "ERR_CHILD_PROCESS_STDIO_MAXBUFFER" && options.allowPartial === true) {
|
|
1701
|
+
const partialOutput = String(error.stdout || "");
|
|
1702
|
+
const lastCompleteRecord = partialOutput.lastIndexOf("\0");
|
|
1703
|
+
const statusByPath = parseGitStatus(lastCompleteRecord >= 0 ? partialOutput.slice(0, lastCompleteRecord + 1) : "");
|
|
1780
1704
|
statusByPath.truncated = true;
|
|
1781
1705
|
return statusByPath;
|
|
1782
1706
|
}
|
|
1783
|
-
if (error.code ===
|
|
1707
|
+
if (error.code === "ENOENT") {
|
|
1784
1708
|
if (options.throwOnError === true) {
|
|
1785
|
-
throw new WorkspaceFileError(
|
|
1709
|
+
throw new WorkspaceFileError("git is not installed", 501);
|
|
1786
1710
|
}
|
|
1787
|
-
return new Map();
|
|
1711
|
+
return /* @__PURE__ */ new Map();
|
|
1788
1712
|
}
|
|
1789
|
-
if (error.code ===
|
|
1790
|
-
throw new WorkspaceFileError(
|
|
1713
|
+
if (error.code === "ETIMEDOUT" || error.signal === "SIGTERM") {
|
|
1714
|
+
throw new WorkspaceFileError("git status timed out", 504);
|
|
1791
1715
|
}
|
|
1792
|
-
if (error.stderr && /not a git repository/i.test(error.stderr)) return new Map();
|
|
1716
|
+
if (error.stderr && /not a git repository/i.test(String(error.stderr))) return /* @__PURE__ */ new Map();
|
|
1793
1717
|
if (options.throwOnError === true) {
|
|
1794
|
-
throw new WorkspaceFileError(error.stderr ||
|
|
1718
|
+
throw new WorkspaceFileError(String(error.stderr || "git status failed"), 500);
|
|
1795
1719
|
}
|
|
1796
|
-
return new Map();
|
|
1720
|
+
return /* @__PURE__ */ new Map();
|
|
1797
1721
|
}
|
|
1798
1722
|
}
|
|
1799
|
-
|
|
1800
1723
|
async loadGitIgnoredPaths(root, relativePaths) {
|
|
1801
|
-
const paths = Array.from(new Set(relativePaths
|
|
1802
|
-
|
|
1803
|
-
.filter(Boolean)));
|
|
1804
|
-
if (paths.length === 0) return new Set();
|
|
1805
|
-
|
|
1724
|
+
const paths = Array.from(new Set(relativePaths.map(normalizeGitStatusPath).filter(Boolean)));
|
|
1725
|
+
if (paths.length === 0) return /* @__PURE__ */ new Set();
|
|
1806
1726
|
try {
|
|
1807
1727
|
const { stdout } = await this.execFile(this.gitPath, [
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
...paths
|
|
1728
|
+
"check-ignore",
|
|
1729
|
+
"--",
|
|
1730
|
+
...paths
|
|
1811
1731
|
], { cwd: root });
|
|
1812
|
-
return new Set(stdout
|
|
1813
|
-
.split(/\r?\n/)
|
|
1814
|
-
.map(normalizeGitStatusPath)
|
|
1815
|
-
.filter(Boolean));
|
|
1732
|
+
return new Set(String(stdout).split(/\r?\n/).map(normalizeGitStatusPath).filter(Boolean));
|
|
1816
1733
|
} catch {
|
|
1817
|
-
return new Set();
|
|
1734
|
+
return /* @__PURE__ */ new Set();
|
|
1818
1735
|
}
|
|
1819
1736
|
}
|
|
1820
|
-
|
|
1821
1737
|
async loadGitStatusForPath(root, relativePath) {
|
|
1822
1738
|
const normalizedPath = normalizeGitStatusPath(relativePath);
|
|
1823
1739
|
if (!normalizedPath) return null;
|
|
1824
|
-
|
|
1825
1740
|
try {
|
|
1826
1741
|
const { stdout } = await this.execFile(this.gitPath, [
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
normalizedPath
|
|
1742
|
+
"status",
|
|
1743
|
+
"--porcelain=v1",
|
|
1744
|
+
"-z",
|
|
1745
|
+
"--untracked-files=normal",
|
|
1746
|
+
"--ignored=no",
|
|
1747
|
+
"--",
|
|
1748
|
+
normalizedPath
|
|
1834
1749
|
], { cwd: root });
|
|
1835
1750
|
return parseGitStatus(stdout).get(normalizedPath) || null;
|
|
1836
|
-
} catch (
|
|
1837
|
-
|
|
1838
|
-
if (error.
|
|
1751
|
+
} catch (caught) {
|
|
1752
|
+
const error = processError(caught);
|
|
1753
|
+
if (error.code === "ENOENT") return null;
|
|
1754
|
+
if (error.stderr && /not a git repository/i.test(String(error.stderr))) return null;
|
|
1839
1755
|
return null;
|
|
1840
1756
|
}
|
|
1841
1757
|
}
|
|
1842
|
-
|
|
1843
1758
|
async getGitStatusByPath(root) {
|
|
1844
1759
|
if (this.gitStatusCacheTtlMs > 0) {
|
|
1845
1760
|
const now = Date.now();
|
|
@@ -1850,11 +1765,10 @@ class WorkspaceFileService {
|
|
|
1850
1765
|
if (cached?.promise) {
|
|
1851
1766
|
return cached.promise;
|
|
1852
1767
|
}
|
|
1853
|
-
|
|
1854
1768
|
const promise = this.loadGitStatusByPath(root).then((value) => {
|
|
1855
1769
|
this.gitStatusCache.set(root, {
|
|
1856
1770
|
value,
|
|
1857
|
-
expiresAt: Date.now() + this.gitStatusCacheTtlMs
|
|
1771
|
+
expiresAt: Date.now() + this.gitStatusCacheTtlMs
|
|
1858
1772
|
});
|
|
1859
1773
|
return value;
|
|
1860
1774
|
}, (error) => {
|
|
@@ -1863,113 +1777,101 @@ class WorkspaceFileService {
|
|
|
1863
1777
|
});
|
|
1864
1778
|
this.gitStatusCache.set(root, {
|
|
1865
1779
|
promise,
|
|
1866
|
-
expiresAt: now + this.gitStatusCacheTtlMs
|
|
1780
|
+
expiresAt: now + this.gitStatusCacheTtlMs
|
|
1867
1781
|
});
|
|
1868
1782
|
return promise;
|
|
1869
1783
|
}
|
|
1870
|
-
|
|
1871
1784
|
return this.loadGitStatusByPath(root);
|
|
1872
1785
|
}
|
|
1873
|
-
|
|
1874
1786
|
async getGitStatusForTree(root) {
|
|
1875
1787
|
const cached = this.gitStatusCache.get(root);
|
|
1876
1788
|
if (cached?.value) {
|
|
1877
1789
|
return { value: cached.value, pending: false };
|
|
1878
1790
|
}
|
|
1879
|
-
|
|
1880
1791
|
const promise = this.getGitStatusByPath(root);
|
|
1881
1792
|
if (this.gitStatusInlineTimeoutMs <= 0) {
|
|
1882
1793
|
return { value: await promise, pending: false };
|
|
1883
1794
|
}
|
|
1884
|
-
|
|
1885
1795
|
const result = await Promise.race([
|
|
1886
1796
|
promise,
|
|
1887
|
-
new Promise(resolve => setTimeout(() => resolve(TIMEOUT), this.gitStatusInlineTimeoutMs))
|
|
1797
|
+
new Promise((resolve) => setTimeout(() => resolve(TIMEOUT), this.gitStatusInlineTimeoutMs))
|
|
1888
1798
|
]);
|
|
1889
|
-
|
|
1890
1799
|
if (result === TIMEOUT) {
|
|
1891
|
-
return { value: new Map(), pending: true };
|
|
1800
|
+
return { value: /* @__PURE__ */ new Map(), pending: true };
|
|
1892
1801
|
}
|
|
1893
|
-
|
|
1894
1802
|
return { value: result, pending: false };
|
|
1895
1803
|
}
|
|
1896
|
-
|
|
1897
1804
|
async getGitStatusForPath(root, relativePath) {
|
|
1898
1805
|
const normalizedPath = normalizeGitStatusPath(relativePath);
|
|
1899
1806
|
if (!normalizedPath) return null;
|
|
1900
|
-
|
|
1901
1807
|
const cached = this.gitStatusCache.get(root);
|
|
1902
1808
|
if (cached?.value) {
|
|
1903
1809
|
return cached.value.get(normalizedPath) || null;
|
|
1904
1810
|
}
|
|
1905
|
-
|
|
1906
1811
|
return this.loadGitStatusForPath(root, normalizedPath);
|
|
1907
1812
|
}
|
|
1908
|
-
|
|
1909
1813
|
async gitBranch(workspaceRoot) {
|
|
1910
1814
|
const root = await this.resolveRoot(workspaceRoot);
|
|
1911
1815
|
try {
|
|
1912
|
-
const { stdout } = await this.execFile(this.gitPath, [
|
|
1816
|
+
const { stdout } = await this.execFile(this.gitPath, ["-C", root, "branch", "--show-current"], {
|
|
1913
1817
|
timeout: 1500,
|
|
1914
|
-
maxBuffer: 64 * 1024
|
|
1818
|
+
maxBuffer: 64 * 1024
|
|
1915
1819
|
});
|
|
1916
|
-
return String(stdout ||
|
|
1820
|
+
return String(stdout || "").trim();
|
|
1917
1821
|
} catch {
|
|
1918
|
-
return
|
|
1822
|
+
return "";
|
|
1919
1823
|
}
|
|
1920
1824
|
}
|
|
1921
|
-
|
|
1922
1825
|
async gitHistory(workspaceRoot, options = {}) {
|
|
1923
1826
|
const root = await this.resolveRoot(workspaceRoot);
|
|
1924
1827
|
const limit = normalizeGitHistoryLimit(options.limit);
|
|
1925
1828
|
const skip = normalizeGitHistorySkip(options.skip);
|
|
1926
|
-
const scope = options.scope ===
|
|
1927
|
-
let branch =
|
|
1829
|
+
const scope = options.scope === "all" ? "all" : "current";
|
|
1830
|
+
let branch = "";
|
|
1928
1831
|
try {
|
|
1929
1832
|
branch = await this.gitBranch(root);
|
|
1930
|
-
await this.execFile(this.gitPath, [
|
|
1833
|
+
await this.execFile(this.gitPath, ["-C", root, "rev-parse", "--is-inside-work-tree"], {
|
|
1931
1834
|
timeout: 1500,
|
|
1932
|
-
maxBuffer: 64 * 1024
|
|
1835
|
+
maxBuffer: 64 * 1024
|
|
1933
1836
|
});
|
|
1934
|
-
} catch (
|
|
1935
|
-
|
|
1837
|
+
} catch (caught) {
|
|
1838
|
+
const error = processError(caught);
|
|
1839
|
+
if (error.code === "ENOENT") throw new WorkspaceFileError("git is not installed", 501);
|
|
1936
1840
|
if (error.stderr && /not a git repository/i.test(String(error.stderr))) {
|
|
1937
|
-
return { isGitRepo: false, branch:
|
|
1841
|
+
return { isGitRepo: false, branch: "", head: "", scope, items: [], hasMore: false, nextSkip: null };
|
|
1938
1842
|
}
|
|
1939
|
-
throw new WorkspaceFileError(error.stderr ||
|
|
1843
|
+
throw new WorkspaceFileError(String(error.stderr || "git history failed"), 500);
|
|
1940
1844
|
}
|
|
1941
|
-
|
|
1942
|
-
let head = '';
|
|
1845
|
+
let head = "";
|
|
1943
1846
|
try {
|
|
1944
|
-
const result = await this.execFile(this.gitPath, [
|
|
1847
|
+
const result = await this.execFile(this.gitPath, ["-C", root, "rev-parse", "--verify", "HEAD"], {
|
|
1945
1848
|
timeout: 1500,
|
|
1946
|
-
maxBuffer: 64 * 1024
|
|
1849
|
+
maxBuffer: 64 * 1024
|
|
1947
1850
|
});
|
|
1948
|
-
head = String(result.stdout ||
|
|
1949
|
-
} catch (
|
|
1851
|
+
head = String(result.stdout || "").trim();
|
|
1852
|
+
} catch (caught) {
|
|
1853
|
+
const error = processError(caught);
|
|
1950
1854
|
if (error.stderr && /(needed a single revision|unknown revision|bad revision|ambiguous argument)/i.test(String(error.stderr))) {
|
|
1951
|
-
return { isGitRepo: true, branch, head:
|
|
1855
|
+
return { isGitRepo: true, branch, head: "", scope, items: [], hasMore: false, nextSkip: null };
|
|
1952
1856
|
}
|
|
1953
|
-
throw new WorkspaceFileError(error.stderr ||
|
|
1857
|
+
throw new WorkspaceFileError(String(error.stderr || "git history failed"), 500);
|
|
1954
1858
|
}
|
|
1955
|
-
|
|
1956
1859
|
try {
|
|
1957
|
-
const revisionArgs = scope ===
|
|
1958
|
-
? ['HEAD', '--branches', '--tags', '--remotes']
|
|
1959
|
-
: ['--first-parent', 'HEAD'];
|
|
1860
|
+
const revisionArgs = scope === "all" ? ["HEAD", "--branches", "--tags", "--remotes"] : ["--first-parent", "HEAD"];
|
|
1960
1861
|
const { stdout } = await this.execFile(this.gitPath, [
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1862
|
+
"-C",
|
|
1863
|
+
root,
|
|
1864
|
+
"log",
|
|
1865
|
+
"-z",
|
|
1866
|
+
"--date-order",
|
|
1867
|
+
"--decorate=full",
|
|
1868
|
+
"--format=%H%x00%P%x00%an%x00%ae%x00%aI%x00%s%x00%B%x00%D",
|
|
1967
1869
|
`--max-count=${limit + 1}`,
|
|
1968
1870
|
`--skip=${skip}`,
|
|
1969
|
-
...revisionArgs
|
|
1871
|
+
...revisionArgs
|
|
1970
1872
|
], {
|
|
1971
1873
|
timeout: this.gitHistoryTimeoutMs,
|
|
1972
|
-
maxBuffer: this.gitHistoryMaxBuffer
|
|
1874
|
+
maxBuffer: this.gitHistoryMaxBuffer
|
|
1973
1875
|
});
|
|
1974
1876
|
const parsed = parseGitHistoryLog(stdout);
|
|
1975
1877
|
const hasMore = parsed.length > limit;
|
|
@@ -1981,65 +1883,67 @@ class WorkspaceFileService {
|
|
|
1981
1883
|
scope,
|
|
1982
1884
|
items,
|
|
1983
1885
|
hasMore,
|
|
1984
|
-
nextSkip: hasMore ? skip + items.length : null
|
|
1886
|
+
nextSkip: hasMore ? skip + items.length : null
|
|
1985
1887
|
};
|
|
1986
|
-
} catch (
|
|
1987
|
-
|
|
1988
|
-
if (error.code ===
|
|
1989
|
-
|
|
1888
|
+
} catch (caught) {
|
|
1889
|
+
const error = processError(caught);
|
|
1890
|
+
if (error.code === "ENOENT") throw new WorkspaceFileError("git is not installed", 501);
|
|
1891
|
+
if (error.code === "ETIMEDOUT" || error.signal === "SIGTERM") {
|
|
1892
|
+
throw new WorkspaceFileError("git history timed out", 504);
|
|
1990
1893
|
}
|
|
1991
|
-
if (error.code ===
|
|
1992
|
-
throw new WorkspaceFileError(
|
|
1894
|
+
if (error.code === "ERR_CHILD_PROCESS_STDIO_MAXBUFFER") {
|
|
1895
|
+
throw new WorkspaceFileError("git history output is too large", 413);
|
|
1993
1896
|
}
|
|
1994
1897
|
if (error.stderr && /not a git repository/i.test(String(error.stderr))) {
|
|
1995
|
-
return { isGitRepo: false, branch:
|
|
1898
|
+
return { isGitRepo: false, branch: "", head: "", scope, items: [], hasMore: false, nextSkip: null };
|
|
1996
1899
|
}
|
|
1997
|
-
throw new WorkspaceFileError(error.stderr ||
|
|
1900
|
+
throw new WorkspaceFileError(String(error.stderr || "git history failed"), 500);
|
|
1998
1901
|
}
|
|
1999
1902
|
}
|
|
2000
|
-
|
|
2001
1903
|
async gitHistoryChanges(workspaceRoot, commitValue, parentValue, options = {}) {
|
|
2002
1904
|
const root = await this.resolveRoot(workspaceRoot);
|
|
2003
1905
|
const commit = normalizeGitObjectId(commitValue);
|
|
2004
|
-
const requestedParent = parentValue ? normalizeGitObjectId(parentValue,
|
|
2005
|
-
const limit = Math.max(1, Math.min(
|
|
2006
|
-
|
|
1906
|
+
const requestedParent = parentValue ? normalizeGitObjectId(parentValue, "parent") : "";
|
|
1907
|
+
const limit = Math.max(1, Math.min(2e3, Number(options.limit) || DEFAULT_GIT_CHANGES_LIMIT));
|
|
2007
1908
|
try {
|
|
2008
1909
|
const { stdout: parentOutput } = await this.execFile(this.gitPath, [
|
|
2009
|
-
|
|
1910
|
+
"-C",
|
|
1911
|
+
root,
|
|
1912
|
+
"rev-list",
|
|
1913
|
+
"--parents",
|
|
1914
|
+
"-n",
|
|
1915
|
+
"1",
|
|
1916
|
+
commit
|
|
2010
1917
|
], {
|
|
2011
1918
|
timeout: this.gitHistoryTimeoutMs,
|
|
2012
|
-
maxBuffer: 64 * 1024
|
|
1919
|
+
maxBuffer: 64 * 1024
|
|
2013
1920
|
});
|
|
2014
|
-
const revision = String(parentOutput ||
|
|
1921
|
+
const revision = String(parentOutput || "").trim().split(/\s+/).filter(Boolean);
|
|
2015
1922
|
if (!revision.length || revision[0].toLowerCase() !== commit) {
|
|
2016
|
-
throw new WorkspaceFileError(
|
|
1923
|
+
throw new WorkspaceFileError("commit was not found", 404);
|
|
2017
1924
|
}
|
|
2018
|
-
const parentIds = revision.slice(1).map(value => value.toLowerCase());
|
|
1925
|
+
const parentIds = revision.slice(1).map((value) => value.toLowerCase());
|
|
2019
1926
|
if (requestedParent && !parentIds.includes(requestedParent)) {
|
|
2020
|
-
throw new WorkspaceFileError(
|
|
1927
|
+
throw new WorkspaceFileError("parent is not a parent of commit", 400);
|
|
2021
1928
|
}
|
|
2022
|
-
const parent = requestedParent || parentIds[0] ||
|
|
1929
|
+
const parent = requestedParent || parentIds[0] || "";
|
|
2023
1930
|
let comparisonBase = parent;
|
|
2024
1931
|
if (!comparisonBase) {
|
|
2025
|
-
let objectFormat =
|
|
1932
|
+
let objectFormat = "sha1";
|
|
2026
1933
|
try {
|
|
2027
|
-
const result = await this.execFile(this.gitPath, [
|
|
1934
|
+
const result = await this.execFile(this.gitPath, ["-C", root, "rev-parse", "--show-object-format"], {
|
|
2028
1935
|
timeout: 1500,
|
|
2029
|
-
maxBuffer: 64 * 1024
|
|
1936
|
+
maxBuffer: 64 * 1024
|
|
2030
1937
|
});
|
|
2031
|
-
objectFormat = String(result.stdout ||
|
|
1938
|
+
objectFormat = String(result.stdout || "").trim();
|
|
2032
1939
|
} catch {
|
|
2033
|
-
// Git versions without --show-object-format use SHA-1 repositories.
|
|
2034
1940
|
}
|
|
2035
1941
|
comparisonBase = gitEmptyTreeObjectId(objectFormat);
|
|
2036
1942
|
}
|
|
2037
|
-
const args = parent
|
|
2038
|
-
? ['-C', root, 'diff', '--name-status', '-z', '-M', parent, commit, '--']
|
|
2039
|
-
: ['-C', root, 'diff-tree', '--root', '--no-commit-id', '--name-status', '-r', '-z', '-M', commit];
|
|
1943
|
+
const args = parent ? ["-C", root, "diff", "--name-status", "-z", "-M", parent, commit, "--"] : ["-C", root, "diff-tree", "--root", "--no-commit-id", "--name-status", "-r", "-z", "-M", commit];
|
|
2040
1944
|
const { stdout } = await this.execFile(this.gitPath, args, {
|
|
2041
1945
|
timeout: this.gitHistoryTimeoutMs,
|
|
2042
|
-
maxBuffer: this.gitHistoryMaxBuffer
|
|
1946
|
+
maxBuffer: this.gitHistoryMaxBuffer
|
|
2043
1947
|
});
|
|
2044
1948
|
const allItems = parseGitHistoryChanges(stdout);
|
|
2045
1949
|
return {
|
|
@@ -2048,97 +1952,91 @@ class WorkspaceFileService {
|
|
|
2048
1952
|
parent: parent || null,
|
|
2049
1953
|
parentIds,
|
|
2050
1954
|
items: allItems.slice(0, limit),
|
|
2051
|
-
truncated: allItems.length > limit
|
|
1955
|
+
truncated: allItems.length > limit
|
|
2052
1956
|
};
|
|
2053
|
-
} catch (
|
|
1957
|
+
} catch (caught) {
|
|
1958
|
+
const error = processError(caught);
|
|
2054
1959
|
if (error instanceof WorkspaceFileError) throw error;
|
|
2055
|
-
if (error.code ===
|
|
2056
|
-
if (error.code ===
|
|
2057
|
-
throw new WorkspaceFileError(
|
|
1960
|
+
if (error.code === "ENOENT") throw new WorkspaceFileError("git is not installed", 501);
|
|
1961
|
+
if (error.code === "ETIMEDOUT" || error.signal === "SIGTERM") {
|
|
1962
|
+
throw new WorkspaceFileError("git commit changes timed out", 504);
|
|
2058
1963
|
}
|
|
2059
|
-
if (error.code ===
|
|
2060
|
-
throw new WorkspaceFileError(
|
|
1964
|
+
if (error.code === "ERR_CHILD_PROCESS_STDIO_MAXBUFFER") {
|
|
1965
|
+
throw new WorkspaceFileError("git commit changes output is too large", 413);
|
|
2061
1966
|
}
|
|
2062
1967
|
if (error.stderr && /(bad object|unknown revision|ambiguous argument|not a valid object name)/i.test(String(error.stderr))) {
|
|
2063
|
-
throw new WorkspaceFileError(
|
|
1968
|
+
throw new WorkspaceFileError("commit was not found", 404);
|
|
2064
1969
|
}
|
|
2065
1970
|
if (error.stderr && /not a git repository/i.test(String(error.stderr))) {
|
|
2066
|
-
throw new WorkspaceFileError(
|
|
1971
|
+
throw new WorkspaceFileError("workspace is not a git repository", 409);
|
|
2067
1972
|
}
|
|
2068
|
-
throw new WorkspaceFileError(error.stderr ||
|
|
1973
|
+
throw new WorkspaceFileError(String(error.stderr || "git commit changes failed"), 500);
|
|
2069
1974
|
}
|
|
2070
1975
|
}
|
|
2071
|
-
|
|
2072
1976
|
async changes(workspaceRoot, options = {}) {
|
|
2073
1977
|
const root = await this.resolveRoot(workspaceRoot);
|
|
2074
|
-
const limit = Math.max(1, Math.min(
|
|
1978
|
+
const limit = Math.max(1, Math.min(2e3, Number(options.limit) || DEFAULT_GIT_CHANGES_LIMIT));
|
|
2075
1979
|
const gitStatusByPath = await this.loadGitStatusByPath(root, {
|
|
2076
1980
|
allowPartial: true,
|
|
2077
1981
|
throwOnError: true,
|
|
2078
|
-
untrackedFiles:
|
|
1982
|
+
untrackedFiles: "all"
|
|
2079
1983
|
});
|
|
2080
1984
|
this.invalidateGitStatus(root);
|
|
2081
|
-
const allItems = await Promise.all(Array.from(gitStatusByPath.entries())
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
.sort((left, right) => (
|
|
2092
|
-
gitStatusReviewRank(left.gitStatus) - gitStatusReviewRank(right.gitStatus)
|
|
2093
|
-
|| left.path.localeCompare(right.path)
|
|
2094
|
-
));
|
|
2095
|
-
|
|
1985
|
+
const allItems = await Promise.all(Array.from(gitStatusByPath.entries()).map(async ([filePath, status]) => ({
|
|
1986
|
+
path: filePath,
|
|
1987
|
+
name: path.posix.basename(filePath),
|
|
1988
|
+
type: await workspaceEntryTypeForGitChange(root, filePath),
|
|
1989
|
+
gitStatus: status.kind,
|
|
1990
|
+
gitStatusLabel: status.label,
|
|
1991
|
+
...status.previousPath ? { previousPath: status.previousPath } : {}
|
|
1992
|
+
})));
|
|
1993
|
+
allItems.sort((left, right) => gitStatusReviewRank(left.gitStatus) - gitStatusReviewRank(right.gitStatus) || left.path.localeCompare(right.path));
|
|
1994
|
+
const typedGitStatusByPath = gitStatusByPath;
|
|
2096
1995
|
return {
|
|
2097
1996
|
items: allItems.slice(0, limit),
|
|
2098
|
-
truncated:
|
|
1997
|
+
truncated: typedGitStatusByPath.truncated === true || allItems.length > limit
|
|
2099
1998
|
};
|
|
2100
1999
|
}
|
|
2101
|
-
|
|
2102
2000
|
async readFile(workspaceRoot, userPath, options = {}) {
|
|
2103
2001
|
const { target, relativePath, external, readOnly, symbolicLink } = await this.resolvePath(workspaceRoot, userPath, options);
|
|
2104
2002
|
const stat = await fsp.stat(target);
|
|
2105
2003
|
if (!stat.isFile()) {
|
|
2106
|
-
throw new WorkspaceFileError(
|
|
2004
|
+
throw new WorkspaceFileError("path must be a file", 400);
|
|
2107
2005
|
}
|
|
2108
2006
|
const preview = previewForPath(relativePath);
|
|
2109
2007
|
if (preview) {
|
|
2110
2008
|
if (stat.size > this.maxPreviewFileSize) {
|
|
2111
|
-
throw new WorkspaceFileError(
|
|
2009
|
+
throw new WorkspaceFileError("file is too large to preview", 413, { size: stat.size });
|
|
2112
2010
|
}
|
|
2113
|
-
const
|
|
2011
|
+
const buffer2 = await fsp.readFile(target);
|
|
2114
2012
|
return {
|
|
2115
2013
|
path: relativePath,
|
|
2116
|
-
content:
|
|
2014
|
+
content: "",
|
|
2117
2015
|
size: stat.size,
|
|
2118
2016
|
mtimeMs: stat.mtimeMs,
|
|
2119
|
-
sha1: sha1(
|
|
2017
|
+
sha1: sha1(buffer2),
|
|
2120
2018
|
binary: true,
|
|
2121
2019
|
preview,
|
|
2122
|
-
...
|
|
2123
|
-
...
|
|
2124
|
-
...
|
|
2020
|
+
...symbolicLink ? { symbolicLink: true } : {},
|
|
2021
|
+
...external ? { external: true } : {},
|
|
2022
|
+
...readOnly ? { readOnly: true } : {}
|
|
2125
2023
|
};
|
|
2126
2024
|
}
|
|
2127
2025
|
if (await isProbablyBinaryFile(target)) {
|
|
2128
2026
|
return {
|
|
2129
2027
|
path: relativePath,
|
|
2130
|
-
content:
|
|
2028
|
+
content: "",
|
|
2131
2029
|
size: stat.size,
|
|
2132
2030
|
mtimeMs: stat.mtimeMs,
|
|
2133
2031
|
sha1: metadataFileVersion(relativePath, stat),
|
|
2134
2032
|
binary: true,
|
|
2135
2033
|
preview: {
|
|
2136
|
-
kind:
|
|
2137
|
-
mediaType:
|
|
2034
|
+
kind: "binary",
|
|
2035
|
+
mediaType: "application/octet-stream"
|
|
2138
2036
|
},
|
|
2139
|
-
...
|
|
2140
|
-
...
|
|
2141
|
-
...
|
|
2037
|
+
...symbolicLink ? { symbolicLink: true } : {},
|
|
2038
|
+
...external ? { external: true } : {},
|
|
2039
|
+
...readOnly ? { readOnly: true } : {}
|
|
2142
2040
|
};
|
|
2143
2041
|
}
|
|
2144
2042
|
if (stat.size > this.maxFileSize) {
|
|
@@ -2149,41 +2047,39 @@ class WorkspaceFileService {
|
|
|
2149
2047
|
mtimeMs: stat.mtimeMs,
|
|
2150
2048
|
sha1: metadataFileVersion(relativePath, stat),
|
|
2151
2049
|
preview: {
|
|
2152
|
-
kind:
|
|
2153
|
-
mediaType:
|
|
2154
|
-
truncated: true
|
|
2050
|
+
kind: "large-text",
|
|
2051
|
+
mediaType: "text/plain",
|
|
2052
|
+
truncated: true
|
|
2155
2053
|
},
|
|
2156
|
-
...
|
|
2157
|
-
...
|
|
2158
|
-
...
|
|
2054
|
+
...symbolicLink ? { symbolicLink: true } : {},
|
|
2055
|
+
...external ? { external: true } : {},
|
|
2056
|
+
...readOnly ? { readOnly: true } : {}
|
|
2159
2057
|
};
|
|
2160
2058
|
}
|
|
2161
|
-
|
|
2162
2059
|
const buffer = await fsp.readFile(target);
|
|
2163
2060
|
return {
|
|
2164
2061
|
path: relativePath,
|
|
2165
|
-
content: buffer.toString(
|
|
2062
|
+
content: buffer.toString("utf8"),
|
|
2166
2063
|
size: stat.size,
|
|
2167
2064
|
mtimeMs: stat.mtimeMs,
|
|
2168
2065
|
sha1: sha1(buffer),
|
|
2169
|
-
...
|
|
2170
|
-
...
|
|
2171
|
-
...
|
|
2066
|
+
...symbolicLink ? { symbolicLink: true } : {},
|
|
2067
|
+
...external ? { external: true } : {},
|
|
2068
|
+
...readOnly ? { readOnly: true } : {}
|
|
2172
2069
|
};
|
|
2173
2070
|
}
|
|
2174
|
-
|
|
2175
2071
|
async readPreviewFile(workspaceRoot, userPath, options = {}) {
|
|
2176
2072
|
const { target, relativePath, external, readOnly, symbolicLink } = await this.resolvePath(workspaceRoot, userPath, options);
|
|
2177
2073
|
const stat = await fsp.stat(target);
|
|
2178
2074
|
if (!stat.isFile()) {
|
|
2179
|
-
throw new WorkspaceFileError(
|
|
2075
|
+
throw new WorkspaceFileError("path must be a file", 400);
|
|
2180
2076
|
}
|
|
2181
2077
|
const preview = previewForPath(relativePath, { includeTextImages: true });
|
|
2182
2078
|
if (!preview) {
|
|
2183
|
-
throw new WorkspaceFileError(
|
|
2079
|
+
throw new WorkspaceFileError("file preview is not available", 415);
|
|
2184
2080
|
}
|
|
2185
2081
|
if (stat.size > this.maxPreviewFileSize) {
|
|
2186
|
-
throw new WorkspaceFileError(
|
|
2082
|
+
throw new WorkspaceFileError("file is too large to preview", 413, { size: stat.size });
|
|
2187
2083
|
}
|
|
2188
2084
|
const buffer = await fsp.readFile(target);
|
|
2189
2085
|
return {
|
|
@@ -2193,20 +2089,19 @@ class WorkspaceFileService {
|
|
|
2193
2089
|
mtimeMs: stat.mtimeMs,
|
|
2194
2090
|
sha1: sha1(buffer),
|
|
2195
2091
|
preview,
|
|
2196
|
-
...
|
|
2197
|
-
...
|
|
2198
|
-
...
|
|
2092
|
+
...symbolicLink ? { symbolicLink: true } : {},
|
|
2093
|
+
...external ? { external: true } : {},
|
|
2094
|
+
...readOnly ? { readOnly: true } : {}
|
|
2199
2095
|
};
|
|
2200
2096
|
}
|
|
2201
|
-
|
|
2202
2097
|
async readResourceFile(workspaceRoot, userPath, options = {}) {
|
|
2203
2098
|
const { target, relativePath } = await this.resolvePath(workspaceRoot, userPath, options);
|
|
2204
2099
|
const stat = await fsp.stat(target);
|
|
2205
2100
|
if (!stat.isFile()) {
|
|
2206
|
-
throw new WorkspaceFileError(
|
|
2101
|
+
throw new WorkspaceFileError("path must be a file", 400);
|
|
2207
2102
|
}
|
|
2208
2103
|
if (stat.size > this.maxPreviewFileSize) {
|
|
2209
|
-
throw new WorkspaceFileError(
|
|
2104
|
+
throw new WorkspaceFileError("file is too large to preview", 413, { size: stat.size });
|
|
2210
2105
|
}
|
|
2211
2106
|
const buffer = await fsp.readFile(target);
|
|
2212
2107
|
return {
|
|
@@ -2214,144 +2109,135 @@ class WorkspaceFileService {
|
|
|
2214
2109
|
buffer,
|
|
2215
2110
|
size: stat.size,
|
|
2216
2111
|
mtimeMs: stat.mtimeMs,
|
|
2217
|
-
sha1: sha1(buffer)
|
|
2112
|
+
sha1: sha1(buffer)
|
|
2218
2113
|
};
|
|
2219
2114
|
}
|
|
2220
|
-
|
|
2221
2115
|
async blameCapability(workspaceRoot, userPath, options = {}) {
|
|
2222
2116
|
const { root, target, relativePath, actualRelativePath, external } = await this.resolvePath(workspaceRoot, userPath, options);
|
|
2223
|
-
const capability = (available, reason =
|
|
2224
|
-
isGitRepo: reason !==
|
|
2117
|
+
const capability = (available, reason = "") => ({
|
|
2118
|
+
isGitRepo: reason !== "not-git-repo" && reason !== "git-unavailable",
|
|
2225
2119
|
path: relativePath,
|
|
2226
2120
|
available,
|
|
2227
|
-
...
|
|
2121
|
+
...reason ? { reason } : {}
|
|
2228
2122
|
});
|
|
2229
2123
|
const stat = await fsp.stat(target);
|
|
2230
2124
|
if (!stat.isFile()) {
|
|
2231
|
-
throw new WorkspaceFileError(
|
|
2125
|
+
throw new WorkspaceFileError("path must be a file", 400);
|
|
2232
2126
|
}
|
|
2233
2127
|
if (external) {
|
|
2234
|
-
return capability(false,
|
|
2128
|
+
return capability(false, "external");
|
|
2235
2129
|
}
|
|
2236
2130
|
if (stat.size > this.maxFileSize) {
|
|
2237
|
-
return capability(false,
|
|
2131
|
+
return capability(false, "too-large");
|
|
2238
2132
|
}
|
|
2239
2133
|
if (await isProbablyBinaryFile(target)) {
|
|
2240
|
-
return capability(false,
|
|
2134
|
+
return capability(false, "binary");
|
|
2241
2135
|
}
|
|
2242
|
-
|
|
2243
2136
|
const gitPath = actualRelativePath || relativePath;
|
|
2244
2137
|
const directGitStatus = await this.getGitStatusForPath(root, gitPath);
|
|
2245
|
-
if (directGitStatus && [
|
|
2138
|
+
if (directGitStatus && ["added", "deleted", "renamed", "untracked", "conflicted"].includes(directGitStatus.kind)) {
|
|
2246
2139
|
return capability(false, directGitStatus.kind);
|
|
2247
2140
|
}
|
|
2248
|
-
|
|
2249
2141
|
try {
|
|
2250
2142
|
await this.execFile(this.gitPath, [
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
gitPath
|
|
2143
|
+
"ls-files",
|
|
2144
|
+
"--error-unmatch",
|
|
2145
|
+
"--",
|
|
2146
|
+
gitPath
|
|
2255
2147
|
], { cwd: root });
|
|
2256
2148
|
return capability(true);
|
|
2257
|
-
} catch (
|
|
2258
|
-
|
|
2259
|
-
if (error.
|
|
2260
|
-
return capability(false,
|
|
2149
|
+
} catch (caught) {
|
|
2150
|
+
const error = processError(caught);
|
|
2151
|
+
if (error.code === "ENOENT") return capability(false, "git-unavailable");
|
|
2152
|
+
if (error.stderr && /not a git repository/i.test(String(error.stderr))) return capability(false, "not-git-repo");
|
|
2153
|
+
return capability(false, "untracked");
|
|
2261
2154
|
}
|
|
2262
2155
|
}
|
|
2263
|
-
|
|
2264
2156
|
async writeFile(workspaceRoot, userPath, content, options = {}) {
|
|
2265
|
-
return this.runWorkspaceMutation(workspaceRoot, root => (
|
|
2266
|
-
this.writeFileUnlocked(root, userPath, content, options)
|
|
2267
|
-
));
|
|
2157
|
+
return this.runWorkspaceMutation(workspaceRoot, (root) => this.writeFileUnlocked(root, userPath, content, options));
|
|
2268
2158
|
}
|
|
2269
|
-
|
|
2270
2159
|
async flushWorkspaceFileHandle(handle) {
|
|
2271
2160
|
if (!this.flushWorkspaceWrites) return;
|
|
2272
2161
|
try {
|
|
2273
2162
|
await handle.datasync();
|
|
2274
|
-
} catch (
|
|
2275
|
-
|
|
2163
|
+
} catch (caught) {
|
|
2164
|
+
const error = processError(caught);
|
|
2165
|
+
if (!["EINVAL", "ENOSYS", "ENOTSUP", "EOPNOTSUPP"].includes(String(error.code))) {
|
|
2276
2166
|
throw error;
|
|
2277
2167
|
}
|
|
2278
2168
|
this.flushWorkspaceWrites = false;
|
|
2279
|
-
console.warn(
|
|
2169
|
+
console.warn("Workspace file datasync is unsupported; disabling it for this server process:", error);
|
|
2280
2170
|
}
|
|
2281
2171
|
}
|
|
2282
|
-
|
|
2283
2172
|
async writeFileUnlocked(workspaceRoot, userPath, content, options = {}) {
|
|
2284
|
-
if (typeof content !==
|
|
2285
|
-
throw new WorkspaceFileError(
|
|
2173
|
+
if (typeof content !== "string") {
|
|
2174
|
+
throw new WorkspaceFileError("content must be a string", 400);
|
|
2286
2175
|
}
|
|
2287
|
-
if (Buffer.byteLength(content,
|
|
2288
|
-
throw new WorkspaceFileError(
|
|
2176
|
+
if (Buffer.byteLength(content, "utf8") > this.maxWriteSize) {
|
|
2177
|
+
throw new WorkspaceFileError("file is too large to save", 413);
|
|
2289
2178
|
}
|
|
2290
|
-
|
|
2291
2179
|
const { root, target, relativePath } = await this.resolvePath(workspaceRoot, userPath, { allowMissing: true });
|
|
2292
2180
|
let writeTarget = target;
|
|
2293
|
-
const baseSha1 = typeof options.baseSha1 ===
|
|
2181
|
+
const baseSha1 = typeof options.baseSha1 === "string" ? options.baseSha1 : "";
|
|
2294
2182
|
const overwrite = options.overwrite === true;
|
|
2295
|
-
let currentMode =
|
|
2296
|
-
let currentSha1 =
|
|
2183
|
+
let currentMode = 438;
|
|
2184
|
+
let currentSha1 = "";
|
|
2297
2185
|
let currentStat = null;
|
|
2298
2186
|
let exists = false;
|
|
2299
2187
|
let symbolicLink = false;
|
|
2300
|
-
|
|
2301
2188
|
try {
|
|
2302
2189
|
const requestedStat = await fsp.lstat(target);
|
|
2303
2190
|
symbolicLink = requestedStat.isSymbolicLink();
|
|
2304
2191
|
const realTarget = await fsp.realpath(target);
|
|
2305
2192
|
if (!isInside(root, realTarget)) {
|
|
2306
|
-
throw new WorkspaceFileError(
|
|
2193
|
+
throw new WorkspaceFileError("symlinks outside the workspace are not allowed", 403);
|
|
2307
2194
|
}
|
|
2308
2195
|
writeTarget = realTarget;
|
|
2309
|
-
} catch (
|
|
2196
|
+
} catch (caught) {
|
|
2197
|
+
const error = processError(caught);
|
|
2310
2198
|
if (error instanceof WorkspaceFileError) throw error;
|
|
2311
|
-
if (error.code !==
|
|
2199
|
+
if (error.code !== "ENOENT") throw error;
|
|
2312
2200
|
}
|
|
2313
|
-
|
|
2314
2201
|
try {
|
|
2315
2202
|
currentStat = await fsp.stat(writeTarget);
|
|
2316
2203
|
if (!currentStat.isFile()) {
|
|
2317
|
-
throw new WorkspaceFileError(
|
|
2204
|
+
throw new WorkspaceFileError("path must be a file", 400);
|
|
2318
2205
|
}
|
|
2319
2206
|
if (currentStat.size > this.maxFileSize) {
|
|
2320
|
-
throw new WorkspaceFileError(
|
|
2207
|
+
throw new WorkspaceFileError("existing file is too large to overwrite safely", 413, { size: currentStat.size });
|
|
2321
2208
|
}
|
|
2322
2209
|
if (await isProbablyBinaryFile(writeTarget)) {
|
|
2323
|
-
throw new WorkspaceFileError(
|
|
2210
|
+
throw new WorkspaceFileError("binary files cannot be overwritten as text", 415);
|
|
2324
2211
|
}
|
|
2325
2212
|
const currentBuffer = await fsp.readFile(writeTarget);
|
|
2326
2213
|
currentSha1 = sha1(currentBuffer);
|
|
2327
2214
|
currentMode = currentStat.mode;
|
|
2328
2215
|
exists = true;
|
|
2329
|
-
} catch (
|
|
2216
|
+
} catch (caught) {
|
|
2217
|
+
const error = processError(caught);
|
|
2330
2218
|
if (error instanceof WorkspaceFileError) throw error;
|
|
2331
|
-
if (error.code !==
|
|
2219
|
+
if (error.code !== "ENOENT") throw error;
|
|
2332
2220
|
}
|
|
2333
|
-
|
|
2334
|
-
const desiredSha1 = sha1(Buffer.from(content, 'utf8'));
|
|
2221
|
+
const desiredSha1 = sha1(Buffer.from(content, "utf8"));
|
|
2335
2222
|
if (baseSha1 && currentSha1 && baseSha1 !== currentSha1 && !overwrite) {
|
|
2336
2223
|
if (currentSha1 === desiredSha1) {
|
|
2337
2224
|
return {
|
|
2338
2225
|
path: relativePath,
|
|
2339
2226
|
content,
|
|
2340
|
-
size: Buffer.byteLength(content,
|
|
2227
|
+
size: Buffer.byteLength(content, "utf8"),
|
|
2341
2228
|
mtimeMs: currentStat.mtimeMs,
|
|
2342
2229
|
sha1: desiredSha1,
|
|
2343
|
-
...
|
|
2230
|
+
...symbolicLink ? { symbolicLink: true } : {}
|
|
2344
2231
|
};
|
|
2345
2232
|
}
|
|
2346
|
-
throw new WorkspaceFileError(
|
|
2233
|
+
throw new WorkspaceFileError("file changed on disk", 409, {
|
|
2347
2234
|
path: relativePath,
|
|
2348
|
-
currentSha1
|
|
2235
|
+
currentSha1
|
|
2349
2236
|
});
|
|
2350
2237
|
}
|
|
2351
2238
|
if (baseSha1 && !exists && !overwrite) {
|
|
2352
|
-
throw new WorkspaceFileError(
|
|
2239
|
+
throw new WorkspaceFileError("file was deleted on disk", 409, { path: relativePath });
|
|
2353
2240
|
}
|
|
2354
|
-
|
|
2355
2241
|
const tempPath = path.join(
|
|
2356
2242
|
path.dirname(writeTarget),
|
|
2357
2243
|
`.${path.basename(writeTarget)}.farming-${process.pid}-${crypto.randomUUID()}.tmp`
|
|
@@ -2359,189 +2245,171 @@ class WorkspaceFileService {
|
|
|
2359
2245
|
let tempHandle = null;
|
|
2360
2246
|
let committedStat = null;
|
|
2361
2247
|
try {
|
|
2362
|
-
tempHandle = await fsp.open(tempPath,
|
|
2248
|
+
tempHandle = await fsp.open(tempPath, "wx", currentMode);
|
|
2363
2249
|
await tempHandle.writeFile(content);
|
|
2364
2250
|
await this.flushWorkspaceFileHandle(tempHandle);
|
|
2365
2251
|
committedStat = await tempHandle.stat();
|
|
2366
2252
|
await tempHandle.close();
|
|
2367
2253
|
tempHandle = null;
|
|
2368
2254
|
await fsp.rename(tempPath, writeTarget);
|
|
2369
|
-
} catch (
|
|
2255
|
+
} catch (caught) {
|
|
2256
|
+
const error = processError(caught);
|
|
2370
2257
|
if (tempHandle) {
|
|
2371
|
-
await tempHandle.close().catch(() => {
|
|
2258
|
+
await tempHandle.close().catch(() => {
|
|
2259
|
+
});
|
|
2372
2260
|
}
|
|
2373
|
-
await fsp.unlink(tempPath).catch(
|
|
2374
|
-
|
|
2375
|
-
|
|
2261
|
+
await fsp.unlink(tempPath).catch((caughtUnlink) => {
|
|
2262
|
+
const unlinkError = processError(caughtUnlink);
|
|
2263
|
+
if (unlinkError.code !== "ENOENT") {
|
|
2264
|
+
console.error("Failed to clean up workspace file temporary:", unlinkError);
|
|
2376
2265
|
}
|
|
2377
2266
|
});
|
|
2378
2267
|
throw error;
|
|
2379
2268
|
}
|
|
2380
2269
|
this.invalidateGitStatus(root);
|
|
2381
|
-
|
|
2382
2270
|
return {
|
|
2383
2271
|
path: relativePath,
|
|
2384
2272
|
content,
|
|
2385
|
-
size: Buffer.byteLength(content,
|
|
2273
|
+
size: Buffer.byteLength(content, "utf8"),
|
|
2386
2274
|
mtimeMs: committedStat.mtimeMs,
|
|
2387
2275
|
sha1: desiredSha1,
|
|
2388
|
-
...
|
|
2276
|
+
...symbolicLink ? { symbolicLink: true } : {}
|
|
2389
2277
|
};
|
|
2390
2278
|
}
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
return this.runWorkspaceMutation(workspaceRoot, root => (
|
|
2394
|
-
this.moveEntryUnlocked(root, sourcePath, targetDirectory, options)
|
|
2395
|
-
));
|
|
2279
|
+
async moveEntry(workspaceRoot, sourcePath, targetDirectory = "", options = {}) {
|
|
2280
|
+
return this.runWorkspaceMutation(workspaceRoot, (root) => this.moveEntryUnlocked(root, sourcePath, targetDirectory, options));
|
|
2396
2281
|
}
|
|
2397
|
-
|
|
2398
|
-
async moveEntryUnlocked(workspaceRoot, sourcePath, targetDirectory = '', options = {}) {
|
|
2282
|
+
async moveEntryUnlocked(workspaceRoot, sourcePath, targetDirectory = "", options = {}) {
|
|
2399
2283
|
const root = await this.resolveRoot(workspaceRoot);
|
|
2400
2284
|
const normalizedSource = normalizeUserPath(sourcePath);
|
|
2401
2285
|
const normalizedTargetDirectory = normalizeUserPath(targetDirectory);
|
|
2402
|
-
|
|
2403
2286
|
if (!normalizedSource) {
|
|
2404
|
-
throw new WorkspaceFileError(
|
|
2287
|
+
throw new WorkspaceFileError("source path is required", 400);
|
|
2405
2288
|
}
|
|
2406
2289
|
if (shouldIgnorePath(normalizedSource) || shouldIgnorePath(normalizedTargetDirectory)) {
|
|
2407
|
-
throw new WorkspaceFileError(
|
|
2290
|
+
throw new WorkspaceFileError("ignored paths cannot be moved", 403);
|
|
2408
2291
|
}
|
|
2409
|
-
|
|
2410
2292
|
const source = await this.resolveEntryPath(workspaceRoot, normalizedSource);
|
|
2411
2293
|
const targetDirectoryPath = path.resolve(root, normalizedTargetDirectory);
|
|
2412
2294
|
let targetDirectoryRealPath;
|
|
2413
2295
|
try {
|
|
2414
2296
|
targetDirectoryRealPath = await fsp.realpath(targetDirectoryPath);
|
|
2415
2297
|
} catch {
|
|
2416
|
-
throw new WorkspaceFileError(
|
|
2298
|
+
throw new WorkspaceFileError("target directory not found", 404);
|
|
2417
2299
|
}
|
|
2418
2300
|
if (!isInside(root, targetDirectoryRealPath)) {
|
|
2419
|
-
throw new WorkspaceFileError(
|
|
2301
|
+
throw new WorkspaceFileError("target directory must stay inside the workspace", 403);
|
|
2420
2302
|
}
|
|
2421
|
-
|
|
2422
2303
|
const targetDirectoryStat = await fsp.stat(targetDirectoryRealPath);
|
|
2423
2304
|
if (!targetDirectoryStat.isDirectory()) {
|
|
2424
|
-
throw new WorkspaceFileError(
|
|
2305
|
+
throw new WorkspaceFileError("target path must be a directory", 400);
|
|
2425
2306
|
}
|
|
2426
|
-
|
|
2427
2307
|
const target = path.join(targetDirectoryRealPath, path.basename(source.target));
|
|
2428
2308
|
const targetPath = relativeFromRoot(root, target);
|
|
2429
2309
|
if (shouldIgnorePath(targetPath)) {
|
|
2430
|
-
throw new WorkspaceFileError(
|
|
2310
|
+
throw new WorkspaceFileError("ignored paths cannot be moved", 403);
|
|
2431
2311
|
}
|
|
2432
|
-
|
|
2433
2312
|
if (target === source.target) {
|
|
2434
2313
|
return {
|
|
2435
2314
|
sourcePath: source.relativePath,
|
|
2436
2315
|
targetPath,
|
|
2437
2316
|
sourceDirectory: parentDirectory(source.relativePath),
|
|
2438
|
-
targetDirectory: normalizedTargetDirectory
|
|
2317
|
+
targetDirectory: normalizedTargetDirectory
|
|
2439
2318
|
};
|
|
2440
2319
|
}
|
|
2441
|
-
|
|
2442
2320
|
const sourceStat = await fsp.lstat(source.target);
|
|
2443
2321
|
const sourceVersion = workspaceEntryVersion(sourceStat);
|
|
2444
2322
|
if (options.expectedVersion && options.expectedVersion !== sourceVersion) {
|
|
2445
|
-
throw new WorkspaceFileError(
|
|
2323
|
+
throw new WorkspaceFileError("source changed on disk", 409, {
|
|
2446
2324
|
path: source.relativePath,
|
|
2447
|
-
currentVersion: sourceVersion
|
|
2325
|
+
currentVersion: sourceVersion
|
|
2448
2326
|
});
|
|
2449
2327
|
}
|
|
2450
2328
|
if (sourceStat.isDirectory() && isInside(source.target, target)) {
|
|
2451
|
-
throw new WorkspaceFileError(
|
|
2329
|
+
throw new WorkspaceFileError("directory cannot be moved into itself", 400);
|
|
2452
2330
|
}
|
|
2453
|
-
|
|
2454
2331
|
try {
|
|
2455
2332
|
await fsp.lstat(target);
|
|
2456
|
-
throw new WorkspaceFileError(
|
|
2457
|
-
} catch (
|
|
2333
|
+
throw new WorkspaceFileError("target path already exists", 409, { path: targetPath });
|
|
2334
|
+
} catch (caught) {
|
|
2335
|
+
const error = processError(caught);
|
|
2458
2336
|
if (error instanceof WorkspaceFileError) throw error;
|
|
2459
|
-
if (error.code !==
|
|
2337
|
+
if (error.code !== "ENOENT") throw error;
|
|
2460
2338
|
}
|
|
2461
|
-
|
|
2462
2339
|
await fsp.rename(source.target, target);
|
|
2463
2340
|
const targetStat = await fsp.lstat(target);
|
|
2464
2341
|
this.invalidateGitStatus(root);
|
|
2465
|
-
|
|
2466
2342
|
return {
|
|
2467
2343
|
sourcePath: source.relativePath,
|
|
2468
2344
|
targetPath,
|
|
2469
2345
|
sourceDirectory: parentDirectory(source.relativePath),
|
|
2470
2346
|
targetDirectory: normalizedTargetDirectory,
|
|
2471
2347
|
sourceVersion,
|
|
2472
|
-
targetVersion: workspaceEntryVersion(targetStat)
|
|
2348
|
+
targetVersion: workspaceEntryVersion(targetStat)
|
|
2473
2349
|
};
|
|
2474
2350
|
}
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
return this.runWorkspaceMutation(workspaceRoot, root => (
|
|
2478
|
-
this.createEntryUnlocked(root, parentPath, name, type, content)
|
|
2479
|
-
));
|
|
2351
|
+
async createEntry(workspaceRoot, parentPath = "", name, type = "file", content = "") {
|
|
2352
|
+
return this.runWorkspaceMutation(workspaceRoot, (root) => this.createEntryUnlocked(root, parentPath, name, type, content));
|
|
2480
2353
|
}
|
|
2481
|
-
|
|
2482
|
-
async createEntryUnlocked(workspaceRoot, parentPath = '', name, type = 'file', content = '') {
|
|
2354
|
+
async createEntryUnlocked(workspaceRoot, parentPath = "", name, type = "file", content = "") {
|
|
2483
2355
|
const root = await this.resolveRoot(workspaceRoot);
|
|
2484
2356
|
const normalizedParentPath = normalizeUserPath(parentPath);
|
|
2485
2357
|
const entryName = normalizeEntryName(name);
|
|
2486
|
-
const entryType = type ===
|
|
2487
|
-
|
|
2358
|
+
const entryType = type === "directory" ? "directory" : "file";
|
|
2488
2359
|
if (shouldIgnorePath(normalizedParentPath)) {
|
|
2489
|
-
throw new WorkspaceFileError(
|
|
2360
|
+
throw new WorkspaceFileError("ignored paths cannot be changed", 403);
|
|
2490
2361
|
}
|
|
2491
|
-
if (entryType ===
|
|
2492
|
-
throw new WorkspaceFileError(
|
|
2362
|
+
if (entryType === "file" && Buffer.byteLength(String(content || ""), "utf8") > this.maxWriteSize) {
|
|
2363
|
+
throw new WorkspaceFileError("file is too large to save", 413);
|
|
2493
2364
|
}
|
|
2494
|
-
|
|
2495
2365
|
const parentDirectoryPath = path.resolve(root, normalizedParentPath);
|
|
2496
2366
|
let parentDirectoryRealPath;
|
|
2497
2367
|
try {
|
|
2498
2368
|
parentDirectoryRealPath = await fsp.realpath(parentDirectoryPath);
|
|
2499
2369
|
} catch {
|
|
2500
|
-
throw new WorkspaceFileError(
|
|
2370
|
+
throw new WorkspaceFileError("parent directory not found", 404);
|
|
2501
2371
|
}
|
|
2502
2372
|
if (!isInside(root, parentDirectoryRealPath)) {
|
|
2503
|
-
throw new WorkspaceFileError(
|
|
2373
|
+
throw new WorkspaceFileError("parent directory must stay inside the workspace", 403);
|
|
2504
2374
|
}
|
|
2505
|
-
|
|
2506
2375
|
const parentStat = await fsp.stat(parentDirectoryRealPath);
|
|
2507
2376
|
if (!parentStat.isDirectory()) {
|
|
2508
|
-
throw new WorkspaceFileError(
|
|
2377
|
+
throw new WorkspaceFileError("parent path must be a directory", 400);
|
|
2509
2378
|
}
|
|
2510
|
-
|
|
2511
2379
|
const target = path.join(parentDirectoryRealPath, entryName);
|
|
2512
2380
|
const targetPath = relativeFromRoot(root, target);
|
|
2513
2381
|
if (shouldIgnorePath(targetPath)) {
|
|
2514
|
-
throw new WorkspaceFileError(
|
|
2382
|
+
throw new WorkspaceFileError("ignored paths cannot be changed", 403);
|
|
2515
2383
|
}
|
|
2516
|
-
|
|
2517
|
-
if (entryType === 'directory') {
|
|
2384
|
+
if (entryType === "directory") {
|
|
2518
2385
|
try {
|
|
2519
2386
|
await fsp.mkdir(target);
|
|
2520
|
-
} catch (
|
|
2521
|
-
|
|
2522
|
-
|
|
2387
|
+
} catch (caught) {
|
|
2388
|
+
const error = processError(caught);
|
|
2389
|
+
if (error.code === "EEXIST") {
|
|
2390
|
+
throw new WorkspaceFileError("target path already exists", 409, { path: targetPath });
|
|
2523
2391
|
}
|
|
2524
2392
|
throw error;
|
|
2525
2393
|
}
|
|
2526
|
-
const
|
|
2394
|
+
const stat2 = await fsp.lstat(target);
|
|
2527
2395
|
this.invalidateGitStatus(root);
|
|
2528
2396
|
return {
|
|
2529
2397
|
entry: {
|
|
2530
2398
|
name: entryName,
|
|
2531
2399
|
path: targetPath,
|
|
2532
|
-
type:
|
|
2533
|
-
size:
|
|
2534
|
-
mtimeMs:
|
|
2535
|
-
version: workspaceEntryVersion(
|
|
2536
|
-
}
|
|
2400
|
+
type: "directory",
|
|
2401
|
+
size: stat2.size,
|
|
2402
|
+
mtimeMs: stat2.mtimeMs,
|
|
2403
|
+
version: workspaceEntryVersion(stat2)
|
|
2404
|
+
}
|
|
2537
2405
|
};
|
|
2538
2406
|
}
|
|
2539
|
-
|
|
2540
2407
|
try {
|
|
2541
|
-
await fsp.writeFile(target, String(content ||
|
|
2542
|
-
} catch (
|
|
2543
|
-
|
|
2544
|
-
|
|
2408
|
+
await fsp.writeFile(target, String(content || ""), { flag: "wx" });
|
|
2409
|
+
} catch (caught) {
|
|
2410
|
+
const error = processError(caught);
|
|
2411
|
+
if (error.code === "EEXIST") {
|
|
2412
|
+
throw new WorkspaceFileError("target path already exists", 409, { path: targetPath });
|
|
2545
2413
|
}
|
|
2546
2414
|
throw error;
|
|
2547
2415
|
}
|
|
@@ -2551,139 +2419,121 @@ class WorkspaceFileService {
|
|
|
2551
2419
|
entry: {
|
|
2552
2420
|
name: entryName,
|
|
2553
2421
|
path: targetPath,
|
|
2554
|
-
type:
|
|
2422
|
+
type: "file",
|
|
2555
2423
|
size: stat.size,
|
|
2556
2424
|
mtimeMs: stat.mtimeMs,
|
|
2557
|
-
version: workspaceEntryVersion(stat)
|
|
2425
|
+
version: workspaceEntryVersion(stat)
|
|
2558
2426
|
},
|
|
2559
|
-
file: await this.readFile(workspaceRoot, targetPath)
|
|
2427
|
+
file: await this.readFile(workspaceRoot, targetPath)
|
|
2560
2428
|
};
|
|
2561
2429
|
}
|
|
2562
|
-
|
|
2563
2430
|
async renameEntry(workspaceRoot, sourcePath, name, options = {}) {
|
|
2564
|
-
return this.runWorkspaceMutation(workspaceRoot, root => (
|
|
2565
|
-
this.renameEntryUnlocked(root, sourcePath, name, options)
|
|
2566
|
-
));
|
|
2431
|
+
return this.runWorkspaceMutation(workspaceRoot, (root) => this.renameEntryUnlocked(root, sourcePath, name, options));
|
|
2567
2432
|
}
|
|
2568
|
-
|
|
2569
2433
|
async renameEntryUnlocked(workspaceRoot, sourcePath, name, options = {}) {
|
|
2570
2434
|
const root = await this.resolveRoot(workspaceRoot);
|
|
2571
2435
|
const normalizedSource = normalizeUserPath(sourcePath);
|
|
2572
2436
|
const entryName = normalizeEntryName(name);
|
|
2573
|
-
|
|
2574
2437
|
if (!normalizedSource) {
|
|
2575
|
-
throw new WorkspaceFileError(
|
|
2438
|
+
throw new WorkspaceFileError("source path is required", 400);
|
|
2576
2439
|
}
|
|
2577
2440
|
if (shouldIgnorePath(normalizedSource)) {
|
|
2578
|
-
throw new WorkspaceFileError(
|
|
2441
|
+
throw new WorkspaceFileError("ignored paths cannot be changed", 403);
|
|
2579
2442
|
}
|
|
2580
|
-
|
|
2581
2443
|
const source = await this.resolveEntryPath(workspaceRoot, normalizedSource);
|
|
2582
2444
|
const target = path.join(path.dirname(source.target), entryName);
|
|
2583
2445
|
const targetPath = relativeFromRoot(root, target);
|
|
2584
2446
|
if (shouldIgnorePath(targetPath)) {
|
|
2585
|
-
throw new WorkspaceFileError(
|
|
2447
|
+
throw new WorkspaceFileError("ignored paths cannot be changed", 403);
|
|
2586
2448
|
}
|
|
2587
|
-
|
|
2588
2449
|
if (target === source.target) {
|
|
2589
2450
|
return {
|
|
2590
2451
|
sourcePath: source.relativePath,
|
|
2591
2452
|
targetPath,
|
|
2592
2453
|
sourceDirectory: parentDirectory(source.relativePath),
|
|
2593
|
-
targetDirectory: parentDirectory(targetPath)
|
|
2454
|
+
targetDirectory: parentDirectory(targetPath)
|
|
2594
2455
|
};
|
|
2595
2456
|
}
|
|
2596
|
-
|
|
2597
2457
|
const sourceStat = await fsp.lstat(source.target);
|
|
2598
2458
|
const sourceVersion = workspaceEntryVersion(sourceStat);
|
|
2599
2459
|
if (options.expectedVersion && options.expectedVersion !== sourceVersion) {
|
|
2600
|
-
throw new WorkspaceFileError(
|
|
2460
|
+
throw new WorkspaceFileError("source changed on disk", 409, {
|
|
2601
2461
|
path: source.relativePath,
|
|
2602
|
-
currentVersion: sourceVersion
|
|
2462
|
+
currentVersion: sourceVersion
|
|
2603
2463
|
});
|
|
2604
2464
|
}
|
|
2605
|
-
|
|
2606
2465
|
try {
|
|
2607
2466
|
await fsp.lstat(target);
|
|
2608
|
-
throw new WorkspaceFileError(
|
|
2609
|
-
} catch (
|
|
2467
|
+
throw new WorkspaceFileError("target path already exists", 409, { path: targetPath });
|
|
2468
|
+
} catch (caught) {
|
|
2469
|
+
const error = processError(caught);
|
|
2610
2470
|
if (error instanceof WorkspaceFileError) throw error;
|
|
2611
|
-
if (error.code !==
|
|
2471
|
+
if (error.code !== "ENOENT") throw error;
|
|
2612
2472
|
}
|
|
2613
|
-
|
|
2614
2473
|
await fsp.rename(source.target, target);
|
|
2615
2474
|
const targetStat = await fsp.lstat(target);
|
|
2616
2475
|
this.invalidateGitStatus(root);
|
|
2617
|
-
|
|
2618
2476
|
return {
|
|
2619
2477
|
sourcePath: source.relativePath,
|
|
2620
2478
|
targetPath,
|
|
2621
2479
|
sourceDirectory: parentDirectory(source.relativePath),
|
|
2622
2480
|
targetDirectory: parentDirectory(targetPath),
|
|
2623
2481
|
sourceVersion,
|
|
2624
|
-
targetVersion: workspaceEntryVersion(targetStat)
|
|
2482
|
+
targetVersion: workspaceEntryVersion(targetStat)
|
|
2625
2483
|
};
|
|
2626
2484
|
}
|
|
2627
|
-
|
|
2628
2485
|
async deleteEntry(workspaceRoot, userPath, options = {}) {
|
|
2629
|
-
return this.runWorkspaceMutation(workspaceRoot, root => (
|
|
2630
|
-
this.deleteEntryUnlocked(root, userPath, options)
|
|
2631
|
-
));
|
|
2486
|
+
return this.runWorkspaceMutation(workspaceRoot, (root) => this.deleteEntryUnlocked(root, userPath, options));
|
|
2632
2487
|
}
|
|
2633
|
-
|
|
2634
2488
|
async deleteEntryUnlocked(workspaceRoot, userPath, options = {}) {
|
|
2635
2489
|
const { root, target, relativePath } = await this.resolveEntryPath(workspaceRoot, userPath);
|
|
2636
2490
|
if (!relativePath) {
|
|
2637
|
-
throw new WorkspaceFileError(
|
|
2491
|
+
throw new WorkspaceFileError("workspace root cannot be deleted", 400);
|
|
2638
2492
|
}
|
|
2639
2493
|
if (shouldIgnorePath(relativePath)) {
|
|
2640
|
-
throw new WorkspaceFileError(
|
|
2494
|
+
throw new WorkspaceFileError("ignored paths cannot be changed", 403);
|
|
2641
2495
|
}
|
|
2642
|
-
|
|
2643
2496
|
const stat = await fsp.lstat(target);
|
|
2644
2497
|
const version = workspaceEntryVersion(stat);
|
|
2645
2498
|
if (options.expectedVersion && options.expectedVersion !== version) {
|
|
2646
|
-
throw new WorkspaceFileError(
|
|
2499
|
+
throw new WorkspaceFileError("entry changed on disk", 409, {
|
|
2647
2500
|
path: relativePath,
|
|
2648
|
-
currentVersion: version
|
|
2501
|
+
currentVersion: version
|
|
2649
2502
|
});
|
|
2650
2503
|
}
|
|
2651
|
-
const type = stat.isDirectory() ?
|
|
2504
|
+
const type = stat.isDirectory() ? "directory" : stat.isFile() ? "file" : stat.isSymbolicLink() ? "symlink" : "other";
|
|
2652
2505
|
if (stat.isDirectory()) {
|
|
2653
2506
|
await fsp.rm(target, { recursive: true, force: false });
|
|
2654
2507
|
} else {
|
|
2655
2508
|
await fsp.unlink(target);
|
|
2656
2509
|
}
|
|
2657
2510
|
this.invalidateGitStatus(root);
|
|
2658
|
-
|
|
2659
2511
|
return {
|
|
2660
2512
|
path: relativePath,
|
|
2661
2513
|
parentDirectory: parentDirectory(relativePath),
|
|
2662
2514
|
type,
|
|
2663
|
-
version
|
|
2515
|
+
version
|
|
2664
2516
|
};
|
|
2665
2517
|
}
|
|
2666
|
-
|
|
2667
2518
|
async search(workspaceRoot, query, options = {}) {
|
|
2668
|
-
if (typeof query !==
|
|
2669
|
-
throw new WorkspaceFileError(
|
|
2519
|
+
if (typeof query !== "string" || !query.trim()) {
|
|
2520
|
+
throw new WorkspaceFileError("query is required", 400);
|
|
2670
2521
|
}
|
|
2671
|
-
const { root, relativePath } = await this.resolvePath(workspaceRoot, options.path ||
|
|
2522
|
+
const { root, relativePath } = await this.resolvePath(workspaceRoot, options.path || "");
|
|
2672
2523
|
const limit = Math.max(1, Math.min(500, Number(options.limit) || this.searchLimit));
|
|
2673
|
-
const searchPath = relativePath ||
|
|
2674
|
-
const timeout = Math.max(
|
|
2524
|
+
const searchPath = relativePath || ".";
|
|
2525
|
+
const timeout = Math.max(1e3, Number(options.timeoutMs) || this.searchTimeoutMs);
|
|
2675
2526
|
const includeIgnored = options.includeIgnored === true;
|
|
2676
2527
|
const likelyPathQuery = isLikelyPathQuery(query);
|
|
2677
|
-
const result = (
|
|
2528
|
+
const result = (matches2, truncated) => ({
|
|
2678
2529
|
query,
|
|
2679
2530
|
path: searchPath,
|
|
2680
|
-
matches,
|
|
2531
|
+
matches: matches2,
|
|
2681
2532
|
truncated,
|
|
2682
|
-
timeoutMs: timeout
|
|
2533
|
+
timeoutMs: timeout
|
|
2683
2534
|
});
|
|
2684
2535
|
let pathMatchCandidates = [];
|
|
2685
2536
|
let searchOutputTruncated = false;
|
|
2686
|
-
|
|
2687
2537
|
if (includeIgnored) {
|
|
2688
2538
|
try {
|
|
2689
2539
|
const directPathMatch = likelyPathQuery ? await this.directPathMatchCandidate(root, query, true) : null;
|
|
@@ -2692,14 +2542,14 @@ class WorkspaceFileService {
|
|
|
2692
2542
|
}
|
|
2693
2543
|
const ignoredPathSearch = await this.collectGitIgnoredPathMatchCandidates(root, searchPath, query, limit, timeout);
|
|
2694
2544
|
return result(ignoredPathSearch.matches, ignoredPathSearch.truncated);
|
|
2695
|
-
} catch (
|
|
2696
|
-
|
|
2545
|
+
} catch (caught) {
|
|
2546
|
+
const error = processError(caught);
|
|
2547
|
+
if (error.code === "ETIMEDOUT" || error.signal === "SIGTERM" || error.code === "ERR_CHILD_PROCESS_STDIO_MAXBUFFER") {
|
|
2697
2548
|
return result([], true);
|
|
2698
2549
|
}
|
|
2699
2550
|
return result([], false);
|
|
2700
2551
|
}
|
|
2701
2552
|
}
|
|
2702
|
-
|
|
2703
2553
|
try {
|
|
2704
2554
|
const directPathMatch = likelyPathQuery ? await this.directPathMatchCandidate(root, query) : null;
|
|
2705
2555
|
if (directPathMatch) {
|
|
@@ -2709,101 +2559,98 @@ class WorkspaceFileService {
|
|
|
2709
2559
|
const directoryNameSearch = await this.collectDirectoryNameMatchCandidates(root, searchPath, query, limit, timeout);
|
|
2710
2560
|
pathMatchCandidates = dedupePathMatches([
|
|
2711
2561
|
...directoryNameSearch.matches,
|
|
2712
|
-
...pathSearch.matches
|
|
2562
|
+
...pathSearch.matches
|
|
2713
2563
|
], limit);
|
|
2714
2564
|
searchOutputTruncated = pathSearch.truncated || directoryNameSearch.truncated;
|
|
2715
|
-
} catch (
|
|
2716
|
-
|
|
2717
|
-
|
|
2565
|
+
} catch (caught) {
|
|
2566
|
+
const error = processError(caught);
|
|
2567
|
+
if (error.code === "ENOENT") {
|
|
2568
|
+
throw new WorkspaceFileError("ripgrep is not installed", 501);
|
|
2718
2569
|
}
|
|
2719
|
-
if (error.code ===
|
|
2570
|
+
if (error.code === "ETIMEDOUT" || error.signal === "SIGTERM") {
|
|
2720
2571
|
searchOutputTruncated = true;
|
|
2721
2572
|
}
|
|
2722
2573
|
pathMatchCandidates = [];
|
|
2723
2574
|
}
|
|
2724
|
-
|
|
2725
2575
|
if (pathMatchCandidates.length >= limit || pathMatchCandidates.length > 0 && likelyPathQuery) {
|
|
2726
2576
|
return result(pathMatchCandidates, searchOutputTruncated || pathMatchCandidates.length >= limit);
|
|
2727
2577
|
}
|
|
2728
|
-
|
|
2729
2578
|
const args = [
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
|
|
2579
|
+
"--json",
|
|
2580
|
+
"--color",
|
|
2581
|
+
"never",
|
|
2582
|
+
"--line-number",
|
|
2583
|
+
"--column",
|
|
2584
|
+
"--max-count",
|
|
2736
2585
|
String(Math.min(3, limit)),
|
|
2737
|
-
|
|
2586
|
+
"--hidden",
|
|
2738
2587
|
...searchExcludeGlobArgs(),
|
|
2739
|
-
|
|
2588
|
+
"--",
|
|
2740
2589
|
query,
|
|
2741
|
-
searchPath
|
|
2590
|
+
searchPath
|
|
2742
2591
|
];
|
|
2743
|
-
|
|
2744
2592
|
let stdout;
|
|
2745
2593
|
try {
|
|
2746
2594
|
({ stdout } = await this.execRipgrep(args, { cwd: root, timeout }));
|
|
2747
|
-
} catch (
|
|
2595
|
+
} catch (caught) {
|
|
2596
|
+
const error = processError(caught);
|
|
2748
2597
|
if (error.code === 1) {
|
|
2749
|
-
stdout = error.stdout ||
|
|
2750
|
-
} else if (error.code ===
|
|
2751
|
-
throw new WorkspaceFileError(
|
|
2752
|
-
} else if (error.code ===
|
|
2753
|
-
stdout = error.stdout ||
|
|
2598
|
+
stdout = error.stdout || "";
|
|
2599
|
+
} else if (error.code === "ENOENT") {
|
|
2600
|
+
throw new WorkspaceFileError("ripgrep is not installed", 501);
|
|
2601
|
+
} else if (error.code === "ERR_CHILD_PROCESS_STDIO_MAXBUFFER") {
|
|
2602
|
+
stdout = error.stdout || "";
|
|
2754
2603
|
searchOutputTruncated = true;
|
|
2755
|
-
} else if (error.code ===
|
|
2756
|
-
stdout = error.stdout ||
|
|
2604
|
+
} else if (error.code === "ETIMEDOUT" || error.signal === "SIGTERM") {
|
|
2605
|
+
stdout = error.stdout || "";
|
|
2757
2606
|
searchOutputTruncated = true;
|
|
2758
2607
|
} else {
|
|
2759
|
-
console.error(
|
|
2608
|
+
console.error("Workspace search command failed:", {
|
|
2760
2609
|
code: error.code,
|
|
2761
2610
|
signal: error.signal,
|
|
2762
2611
|
message: error.message,
|
|
2763
|
-
stderr: error.stderr
|
|
2612
|
+
stderr: error.stderr
|
|
2764
2613
|
});
|
|
2765
|
-
throw new WorkspaceFileError(error.stderr ||
|
|
2614
|
+
throw new WorkspaceFileError(String(error.stderr || "search failed"), 500);
|
|
2766
2615
|
}
|
|
2767
2616
|
}
|
|
2768
|
-
|
|
2769
2617
|
const matches = [];
|
|
2770
|
-
stdout.split(
|
|
2618
|
+
String(stdout).split("\n").filter(Boolean).forEach((line) => {
|
|
2771
2619
|
let event;
|
|
2772
2620
|
try {
|
|
2773
2621
|
event = JSON.parse(line);
|
|
2774
2622
|
} catch {
|
|
2775
2623
|
return;
|
|
2776
2624
|
}
|
|
2777
|
-
if (event.type !==
|
|
2778
|
-
const data = event.data
|
|
2779
|
-
const submatches = Array.isArray(data.submatches) ? data.submatches : [];
|
|
2780
|
-
const
|
|
2625
|
+
if (!isRecord(event) || event.type !== "match") return;
|
|
2626
|
+
const data = isRecord(event.data) ? event.data : {};
|
|
2627
|
+
const submatches = Array.isArray(data.submatches) ? data.submatches.filter(isRecord) : [];
|
|
2628
|
+
const pathData = isRecord(data.path) ? data.path : {};
|
|
2629
|
+
const linesData = isRecord(data.lines) ? data.lines : {};
|
|
2630
|
+
const resultPath = normalizeSearchResultPath(pathData.text);
|
|
2781
2631
|
if (!resultPath || isSearchIgnoredRelativePath(resultPath)) return;
|
|
2782
2632
|
matches.push({
|
|
2783
|
-
kind:
|
|
2784
|
-
entryType:
|
|
2633
|
+
kind: "content",
|
|
2634
|
+
entryType: "file",
|
|
2785
2635
|
path: resultPath,
|
|
2786
2636
|
lineNumber: data.line_number,
|
|
2787
|
-
lines:
|
|
2788
|
-
ranges: submatches.map(match => ({
|
|
2637
|
+
lines: typeof linesData.text === "string" ? linesData.text.replace(/\n$/, "") : "",
|
|
2638
|
+
ranges: submatches.map((match) => ({
|
|
2789
2639
|
start: match.start,
|
|
2790
|
-
end: match.end
|
|
2791
|
-
}))
|
|
2640
|
+
end: match.end
|
|
2641
|
+
}))
|
|
2792
2642
|
});
|
|
2793
2643
|
});
|
|
2794
|
-
|
|
2795
2644
|
const combinedMatches = [];
|
|
2796
|
-
pathMatchCandidates.forEach(match => {
|
|
2645
|
+
pathMatchCandidates.forEach((match) => {
|
|
2797
2646
|
combinedMatches.push(match);
|
|
2798
2647
|
});
|
|
2799
|
-
matches.forEach(match => {
|
|
2648
|
+
matches.forEach((match) => {
|
|
2800
2649
|
combinedMatches.push(match);
|
|
2801
2650
|
});
|
|
2802
|
-
|
|
2803
2651
|
return result(combinedMatches.slice(0, limit), searchOutputTruncated || combinedMatches.length > limit);
|
|
2804
2652
|
}
|
|
2805
|
-
|
|
2806
|
-
async diff(workspaceRoot, userPath = '', options = {}) {
|
|
2653
|
+
async diff(workspaceRoot, userPath = "", options = {}) {
|
|
2807
2654
|
const root = await this.resolveRoot(workspaceRoot);
|
|
2808
2655
|
const normalized = normalizeUserPath(userPath);
|
|
2809
2656
|
let target = null;
|
|
@@ -2816,7 +2663,8 @@ class WorkspaceFileService {
|
|
|
2816
2663
|
target = resolved.target;
|
|
2817
2664
|
relativePath = resolved.relativePath;
|
|
2818
2665
|
gitRelativePath = resolved.actualRelativePath || resolved.relativePath;
|
|
2819
|
-
} catch (
|
|
2666
|
+
} catch (caught) {
|
|
2667
|
+
const error = processError(caught);
|
|
2820
2668
|
if (error instanceof WorkspaceFileError && error.statusCode === 404) {
|
|
2821
2669
|
targetMissing = true;
|
|
2822
2670
|
} else {
|
|
@@ -2824,46 +2672,42 @@ class WorkspaceFileService {
|
|
|
2824
2672
|
}
|
|
2825
2673
|
}
|
|
2826
2674
|
}
|
|
2827
|
-
const args = [
|
|
2675
|
+
const args = ["-C", root, "diff", ...gitDiffWhitespaceArgs(options.ignoreWhitespace), ...gitDiffContextArgs(options.context), "HEAD", "--"];
|
|
2828
2676
|
if (normalized) args.push(gitRelativePath);
|
|
2829
|
-
|
|
2830
2677
|
try {
|
|
2831
2678
|
const { stdout } = await this.execFile(this.gitPath, args, {
|
|
2832
2679
|
cwd: root,
|
|
2833
2680
|
timeout: this.diffTimeoutMs,
|
|
2834
|
-
maxBuffer: this.diffMaxBuffer
|
|
2681
|
+
maxBuffer: this.diffMaxBuffer
|
|
2835
2682
|
});
|
|
2836
2683
|
const result = {
|
|
2837
2684
|
isGitRepo: true,
|
|
2838
2685
|
path: relativePath,
|
|
2839
|
-
patch: stdout
|
|
2686
|
+
patch: stdout
|
|
2840
2687
|
};
|
|
2841
2688
|
if (!normalized) {
|
|
2842
2689
|
return result;
|
|
2843
2690
|
}
|
|
2844
|
-
|
|
2845
2691
|
if (targetMissing) {
|
|
2846
|
-
const
|
|
2847
|
-
if (
|
|
2848
|
-
throw new WorkspaceFileError(
|
|
2692
|
+
const status2 = await this.getGitStatusForPath(root, gitRelativePath);
|
|
2693
|
+
if (status2?.kind !== "deleted") {
|
|
2694
|
+
throw new WorkspaceFileError("path not found", 404);
|
|
2849
2695
|
}
|
|
2850
2696
|
const original = await this.execFile(this.gitPath, [
|
|
2851
|
-
|
|
2697
|
+
"-C",
|
|
2852
2698
|
root,
|
|
2853
|
-
|
|
2854
|
-
`HEAD:${gitRelativePath}
|
|
2855
|
-
], { cwd: root, encoding:
|
|
2856
|
-
const
|
|
2857
|
-
? original.stdout.toString('utf8')
|
|
2858
|
-
: String(original.stdout || '');
|
|
2699
|
+
"show",
|
|
2700
|
+
`HEAD:${gitRelativePath}`
|
|
2701
|
+
], { cwd: root, encoding: "buffer" });
|
|
2702
|
+
const originalContent2 = Buffer.isBuffer(original.stdout) ? original.stdout.toString("utf8") : String(original.stdout || "");
|
|
2859
2703
|
return {
|
|
2860
2704
|
...result,
|
|
2861
|
-
originalContent,
|
|
2862
|
-
modifiedContent:
|
|
2863
|
-
deleted: true
|
|
2705
|
+
originalContent: originalContent2,
|
|
2706
|
+
modifiedContent: "",
|
|
2707
|
+
deleted: true
|
|
2864
2708
|
};
|
|
2865
2709
|
}
|
|
2866
|
-
|
|
2710
|
+
if (!target) return result;
|
|
2867
2711
|
const stat = await fsp.stat(target);
|
|
2868
2712
|
if (!stat.isFile()) {
|
|
2869
2713
|
return result;
|
|
@@ -2873,96 +2717,89 @@ class WorkspaceFileService {
|
|
|
2873
2717
|
...result,
|
|
2874
2718
|
path: relativePath,
|
|
2875
2719
|
binary: true,
|
|
2876
|
-
size: stat.size
|
|
2720
|
+
size: stat.size
|
|
2877
2721
|
};
|
|
2878
2722
|
}
|
|
2879
|
-
|
|
2880
2723
|
const modifiedBuffer = await fsp.readFile(target);
|
|
2881
2724
|
const gitStatusByPath = await this.loadGitStatusByPath(root);
|
|
2882
2725
|
if (this.gitStatusCacheTtlMs > 0) {
|
|
2883
2726
|
this.gitStatusCache.set(root, {
|
|
2884
2727
|
value: gitStatusByPath,
|
|
2885
|
-
expiresAt: Date.now() + this.gitStatusCacheTtlMs
|
|
2728
|
+
expiresAt: Date.now() + this.gitStatusCacheTtlMs
|
|
2886
2729
|
});
|
|
2887
2730
|
}
|
|
2888
2731
|
const status = gitStatusByPath.get(normalizeGitStatusPath(gitRelativePath)) || null;
|
|
2889
|
-
const originalGitPath = status?.kind ===
|
|
2890
|
-
|
|
2891
|
-
: gitRelativePath;
|
|
2892
|
-
let originalContent = '';
|
|
2732
|
+
const originalGitPath = status?.kind === "renamed" && status.previousPath ? status.previousPath : gitRelativePath;
|
|
2733
|
+
let originalContent = "";
|
|
2893
2734
|
let untracked = false;
|
|
2894
2735
|
try {
|
|
2895
2736
|
const original = await this.execFile(this.gitPath, [
|
|
2896
|
-
|
|
2737
|
+
"-C",
|
|
2897
2738
|
root,
|
|
2898
|
-
|
|
2899
|
-
`HEAD:${originalGitPath}
|
|
2900
|
-
], { cwd: root, encoding:
|
|
2901
|
-
originalContent = Buffer.isBuffer(original.stdout)
|
|
2902
|
-
|
|
2903
|
-
|
|
2904
|
-
} catch (error) {
|
|
2739
|
+
"show",
|
|
2740
|
+
`HEAD:${originalGitPath}`
|
|
2741
|
+
], { cwd: root, encoding: "buffer" });
|
|
2742
|
+
originalContent = Buffer.isBuffer(original.stdout) ? original.stdout.toString("utf8") : String(original.stdout || "");
|
|
2743
|
+
} catch (caught) {
|
|
2744
|
+
const error = processError(caught);
|
|
2905
2745
|
if (error.stderr && /(exists on disk, but not in|path .* exists on disk|does not exist in|not in HEAD|invalid object name)/i.test(String(error.stderr))) {
|
|
2906
2746
|
untracked = true;
|
|
2907
2747
|
} else {
|
|
2908
2748
|
throw error;
|
|
2909
2749
|
}
|
|
2910
2750
|
}
|
|
2911
|
-
|
|
2912
2751
|
return {
|
|
2913
2752
|
...result,
|
|
2914
2753
|
path: relativePath,
|
|
2915
2754
|
originalContent,
|
|
2916
|
-
modifiedContent: modifiedBuffer.toString(
|
|
2917
|
-
untracked
|
|
2755
|
+
modifiedContent: modifiedBuffer.toString("utf8"),
|
|
2756
|
+
untracked
|
|
2918
2757
|
};
|
|
2919
|
-
} catch (
|
|
2920
|
-
|
|
2921
|
-
|
|
2758
|
+
} catch (caught) {
|
|
2759
|
+
const error = processError(caught);
|
|
2760
|
+
if (error.code === "ENOENT") {
|
|
2761
|
+
throw new WorkspaceFileError("git is not installed", 501);
|
|
2922
2762
|
}
|
|
2923
|
-
if (error.code ===
|
|
2924
|
-
throw new WorkspaceFileError(
|
|
2763
|
+
if (error.code === "ETIMEDOUT" || error.signal === "SIGTERM") {
|
|
2764
|
+
throw new WorkspaceFileError("git diff timed out", 504);
|
|
2925
2765
|
}
|
|
2926
|
-
if (error.code ===
|
|
2766
|
+
if (error.code === "ERR_CHILD_PROCESS_STDIO_MAXBUFFER") {
|
|
2927
2767
|
return {
|
|
2928
2768
|
isGitRepo: true,
|
|
2929
2769
|
path: relativePath,
|
|
2930
|
-
patch: error.stdout ||
|
|
2931
|
-
truncated: true
|
|
2770
|
+
patch: error.stdout || "",
|
|
2771
|
+
truncated: true
|
|
2932
2772
|
};
|
|
2933
2773
|
}
|
|
2934
|
-
if (error.stderr && /not a git repository/i.test(error.stderr)) {
|
|
2774
|
+
if (error.stderr && /not a git repository/i.test(String(error.stderr))) {
|
|
2935
2775
|
return {
|
|
2936
2776
|
isGitRepo: false,
|
|
2937
2777
|
path: normalized,
|
|
2938
|
-
patch:
|
|
2778
|
+
patch: ""
|
|
2939
2779
|
};
|
|
2940
2780
|
}
|
|
2941
|
-
throw new WorkspaceFileError(error.stderr ||
|
|
2781
|
+
throw new WorkspaceFileError(String(error.stderr || "git diff failed"), 500);
|
|
2942
2782
|
}
|
|
2943
2783
|
}
|
|
2944
|
-
|
|
2945
|
-
async lineChanges(workspaceRoot, userPath, lineNumber, mode = 'working') {
|
|
2784
|
+
async lineChanges(workspaceRoot, userPath, lineNumber, mode = "working") {
|
|
2946
2785
|
const requestedLineNumber = Number(lineNumber);
|
|
2947
2786
|
if (!Number.isInteger(requestedLineNumber) || requestedLineNumber < 1) {
|
|
2948
|
-
throw new WorkspaceFileError(
|
|
2787
|
+
throw new WorkspaceFileError("lineNumber must be a positive integer", 400);
|
|
2949
2788
|
}
|
|
2950
|
-
if (mode !==
|
|
2951
|
-
throw new WorkspaceFileError(
|
|
2789
|
+
if (mode !== "working" && mode !== "previous") {
|
|
2790
|
+
throw new WorkspaceFileError("mode must be working or previous", 400);
|
|
2952
2791
|
}
|
|
2953
|
-
|
|
2954
2792
|
const { root, target, relativePath, actualRelativePath } = await this.resolvePath(workspaceRoot, userPath);
|
|
2955
2793
|
const stat = await fsp.stat(target);
|
|
2956
2794
|
if (!stat.isFile()) {
|
|
2957
|
-
throw new WorkspaceFileError(
|
|
2795
|
+
throw new WorkspaceFileError("path must be a file", 400);
|
|
2958
2796
|
}
|
|
2959
2797
|
if (stat.size > this.maxFileSize) {
|
|
2960
|
-
throw new WorkspaceFileError(
|
|
2798
|
+
throw new WorkspaceFileError("file is too large to inspect line changes", 413, { size: stat.size });
|
|
2961
2799
|
}
|
|
2962
2800
|
if (await isProbablyBinaryFile(target)) {
|
|
2963
|
-
throw new WorkspaceFileError(
|
|
2801
|
+
throw new WorkspaceFileError("binary files cannot be inspected as text", 415);
|
|
2964
2802
|
}
|
|
2965
|
-
|
|
2966
2803
|
const gitRelativePath = actualRelativePath || relativePath;
|
|
2967
2804
|
const baseResult = {
|
|
2968
2805
|
isGitRepo: true,
|
|
@@ -2970,232 +2807,226 @@ class WorkspaceFileService {
|
|
|
2970
2807
|
mode,
|
|
2971
2808
|
lineNumber: requestedLineNumber,
|
|
2972
2809
|
lookupLineNumber: requestedLineNumber,
|
|
2973
|
-
targetSide: mode ===
|
|
2810
|
+
targetSide: mode === "working" ? "working" : "revision",
|
|
2974
2811
|
available: false,
|
|
2975
|
-
reason:
|
|
2976
|
-
patch:
|
|
2977
|
-
hunk: null
|
|
2812
|
+
reason: "",
|
|
2813
|
+
patch: "",
|
|
2814
|
+
hunk: null
|
|
2978
2815
|
};
|
|
2979
|
-
|
|
2980
2816
|
try {
|
|
2981
|
-
if (mode ===
|
|
2817
|
+
if (mode === "working") {
|
|
2982
2818
|
const directGitStatus = await this.getGitStatusForPath(root, gitRelativePath);
|
|
2983
|
-
if (directGitStatus?.kind ===
|
|
2984
|
-
const modifiedContent = await fsp.readFile(target,
|
|
2985
|
-
const
|
|
2819
|
+
if (directGitStatus?.kind === "untracked") {
|
|
2820
|
+
const modifiedContent = await fsp.readFile(target, "utf8");
|
|
2821
|
+
const hunk3 = createAddedFileLineChangesHunk(modifiedContent, requestedLineNumber);
|
|
2986
2822
|
return {
|
|
2987
2823
|
...baseResult,
|
|
2988
|
-
available: Boolean(
|
|
2989
|
-
reason:
|
|
2990
|
-
patch:
|
|
2991
|
-
hunk
|
|
2824
|
+
available: Boolean(hunk3),
|
|
2825
|
+
reason: hunk3 ? "" : "unchanged",
|
|
2826
|
+
patch: hunk3 ? hunk3.patch : "",
|
|
2827
|
+
hunk: hunk3
|
|
2992
2828
|
};
|
|
2993
2829
|
}
|
|
2994
|
-
|
|
2995
|
-
|
|
2996
|
-
'-C',
|
|
2830
|
+
const { stdout: stdout2 } = await this.execFile(this.gitPath, [
|
|
2831
|
+
"-C",
|
|
2997
2832
|
root,
|
|
2998
|
-
|
|
2999
|
-
|
|
3000
|
-
|
|
3001
|
-
|
|
3002
|
-
gitRelativePath
|
|
2833
|
+
"diff",
|
|
2834
|
+
"--unified=20",
|
|
2835
|
+
"HEAD",
|
|
2836
|
+
"--",
|
|
2837
|
+
gitRelativePath
|
|
3003
2838
|
], {
|
|
3004
2839
|
cwd: root,
|
|
3005
2840
|
timeout: this.diffTimeoutMs,
|
|
3006
|
-
maxBuffer: this.diffMaxBuffer
|
|
2841
|
+
maxBuffer: this.diffMaxBuffer
|
|
3007
2842
|
});
|
|
3008
|
-
const
|
|
2843
|
+
const hunk2 = selectUnifiedDiffHunk(stdout2, "new", requestedLineNumber);
|
|
3009
2844
|
return {
|
|
3010
2845
|
...baseResult,
|
|
3011
|
-
available: Boolean(
|
|
3012
|
-
reason:
|
|
3013
|
-
patch:
|
|
3014
|
-
hunk
|
|
2846
|
+
available: Boolean(hunk2),
|
|
2847
|
+
reason: hunk2 ? "" : "unchanged",
|
|
2848
|
+
patch: hunk2 ? hunk2.patch : "",
|
|
2849
|
+
hunk: hunk2
|
|
3015
2850
|
};
|
|
3016
2851
|
}
|
|
3017
|
-
|
|
3018
2852
|
const blame = await this.blame(workspaceRoot, userPath);
|
|
3019
2853
|
if (!blame.isGitRepo) {
|
|
3020
2854
|
return {
|
|
3021
2855
|
...baseResult,
|
|
3022
2856
|
isGitRepo: false,
|
|
3023
|
-
reason:
|
|
2857
|
+
reason: "not-git-repo"
|
|
3024
2858
|
};
|
|
3025
2859
|
}
|
|
3026
|
-
const blameLine = blame.lines.find(line => line.lineNumber === requestedLineNumber);
|
|
2860
|
+
const blameLine = blame.lines.find((line) => line.lineNumber === requestedLineNumber);
|
|
3027
2861
|
if (!blameLine) {
|
|
3028
2862
|
return {
|
|
3029
2863
|
...baseResult,
|
|
3030
|
-
reason:
|
|
2864
|
+
reason: "line-not-found"
|
|
3031
2865
|
};
|
|
3032
2866
|
}
|
|
3033
2867
|
if (blameLine.uncommitted) {
|
|
3034
2868
|
return {
|
|
3035
2869
|
...baseResult,
|
|
3036
|
-
reason:
|
|
2870
|
+
reason: "uncommitted",
|
|
3037
2871
|
commit: {
|
|
3038
2872
|
hash: blameLine.commit,
|
|
3039
2873
|
shortHash: blameLine.shortCommit,
|
|
3040
2874
|
author: blameLine.author,
|
|
3041
2875
|
authorTimeIso: blameLine.authorTimeIso,
|
|
3042
|
-
summary: blameLine.summary
|
|
3043
|
-
}
|
|
2876
|
+
summary: blameLine.summary
|
|
2877
|
+
}
|
|
3044
2878
|
};
|
|
3045
2879
|
}
|
|
3046
|
-
|
|
3047
2880
|
const { stdout } = await this.execFile(this.gitPath, [
|
|
3048
|
-
|
|
2881
|
+
"-C",
|
|
3049
2882
|
root,
|
|
3050
|
-
|
|
3051
|
-
|
|
3052
|
-
|
|
2883
|
+
"show",
|
|
2884
|
+
"--format=",
|
|
2885
|
+
"--unified=20",
|
|
3053
2886
|
blameLine.commit,
|
|
3054
|
-
|
|
3055
|
-
gitRelativePath
|
|
2887
|
+
"--",
|
|
2888
|
+
gitRelativePath
|
|
3056
2889
|
], {
|
|
3057
2890
|
cwd: root,
|
|
3058
2891
|
timeout: this.diffTimeoutMs,
|
|
3059
|
-
maxBuffer: this.diffMaxBuffer
|
|
2892
|
+
maxBuffer: this.diffMaxBuffer
|
|
3060
2893
|
});
|
|
3061
|
-
const hunk = selectUnifiedDiffHunk(stdout,
|
|
2894
|
+
const hunk = selectUnifiedDiffHunk(stdout, "new", blameLine.originalLineNumber);
|
|
3062
2895
|
return {
|
|
3063
2896
|
...baseResult,
|
|
3064
2897
|
lookupLineNumber: blameLine.originalLineNumber,
|
|
3065
2898
|
available: Boolean(hunk),
|
|
3066
|
-
reason: hunk ?
|
|
3067
|
-
patch: hunk ? hunk.patch :
|
|
2899
|
+
reason: hunk ? "" : "not-found",
|
|
2900
|
+
patch: hunk ? hunk.patch : "",
|
|
3068
2901
|
hunk,
|
|
3069
2902
|
commit: {
|
|
3070
2903
|
hash: blameLine.commit,
|
|
3071
2904
|
shortHash: blameLine.shortCommit,
|
|
3072
2905
|
author: blameLine.author,
|
|
3073
2906
|
authorTimeIso: blameLine.authorTimeIso,
|
|
3074
|
-
summary: blameLine.summary
|
|
3075
|
-
}
|
|
2907
|
+
summary: blameLine.summary
|
|
2908
|
+
}
|
|
3076
2909
|
};
|
|
3077
|
-
} catch (
|
|
2910
|
+
} catch (caught) {
|
|
2911
|
+
const error = processError(caught);
|
|
3078
2912
|
if (error instanceof WorkspaceFileError) {
|
|
3079
|
-
if (mode ===
|
|
2913
|
+
if (mode === "previous" && error.statusCode === 409) {
|
|
3080
2914
|
return {
|
|
3081
2915
|
...baseResult,
|
|
3082
|
-
reason:
|
|
2916
|
+
reason: "untracked"
|
|
3083
2917
|
};
|
|
3084
2918
|
}
|
|
3085
2919
|
throw error;
|
|
3086
2920
|
}
|
|
3087
|
-
if (error.code ===
|
|
3088
|
-
throw new WorkspaceFileError(
|
|
2921
|
+
if (error.code === "ENOENT") {
|
|
2922
|
+
throw new WorkspaceFileError("git is not installed", 501);
|
|
3089
2923
|
}
|
|
3090
|
-
if (error.code ===
|
|
3091
|
-
throw new WorkspaceFileError(
|
|
2924
|
+
if (error.code === "ETIMEDOUT" || error.signal === "SIGTERM") {
|
|
2925
|
+
throw new WorkspaceFileError("git line changes timed out", 504);
|
|
3092
2926
|
}
|
|
3093
|
-
if (error.code ===
|
|
2927
|
+
if (error.code === "ERR_CHILD_PROCESS_STDIO_MAXBUFFER") {
|
|
3094
2928
|
return {
|
|
3095
2929
|
...baseResult,
|
|
3096
2930
|
available: false,
|
|
3097
|
-
reason:
|
|
3098
|
-
patch: error.stdout ||
|
|
3099
|
-
truncated: true
|
|
2931
|
+
reason: "truncated",
|
|
2932
|
+
patch: error.stdout || "",
|
|
2933
|
+
truncated: true
|
|
3100
2934
|
};
|
|
3101
2935
|
}
|
|
3102
|
-
if (error.stderr && /not a git repository/i.test(error.stderr)) {
|
|
2936
|
+
if (error.stderr && /not a git repository/i.test(String(error.stderr))) {
|
|
3103
2937
|
return {
|
|
3104
2938
|
...baseResult,
|
|
3105
2939
|
isGitRepo: false,
|
|
3106
|
-
reason:
|
|
2940
|
+
reason: "not-git-repo"
|
|
3107
2941
|
};
|
|
3108
2942
|
}
|
|
3109
|
-
if (mode ===
|
|
2943
|
+
if (mode === "previous" && error.stderr && /(no such path|does not exist in|not in HEAD|path .* exists on disk|invalid object name)/i.test(String(error.stderr))) {
|
|
3110
2944
|
return {
|
|
3111
2945
|
...baseResult,
|
|
3112
|
-
reason:
|
|
2946
|
+
reason: "not-found"
|
|
3113
2947
|
};
|
|
3114
2948
|
}
|
|
3115
|
-
throw new WorkspaceFileError(error.stderr ||
|
|
2949
|
+
throw new WorkspaceFileError(String(error.stderr || "git line changes failed"), 500);
|
|
3116
2950
|
}
|
|
3117
2951
|
}
|
|
3118
|
-
|
|
3119
2952
|
async blame(workspaceRoot, userPath) {
|
|
3120
2953
|
const { root, target, relativePath, actualRelativePath } = await this.resolvePath(workspaceRoot, userPath);
|
|
3121
2954
|
const stat = await fsp.stat(target);
|
|
3122
2955
|
if (!stat.isFile()) {
|
|
3123
|
-
throw new WorkspaceFileError(
|
|
2956
|
+
throw new WorkspaceFileError("path must be a file", 400);
|
|
3124
2957
|
}
|
|
3125
2958
|
if (stat.size > this.maxFileSize) {
|
|
3126
|
-
throw new WorkspaceFileError(
|
|
2959
|
+
throw new WorkspaceFileError("file is too large to blame", 413, { size: stat.size });
|
|
3127
2960
|
}
|
|
3128
2961
|
if (await isProbablyBinaryFile(target)) {
|
|
3129
|
-
throw new WorkspaceFileError(
|
|
2962
|
+
throw new WorkspaceFileError("binary files cannot be blamed as text", 415);
|
|
3130
2963
|
}
|
|
3131
|
-
|
|
3132
2964
|
try {
|
|
3133
2965
|
const { stdout } = await this.execFile(this.gitPath, [
|
|
3134
|
-
|
|
3135
|
-
|
|
3136
|
-
|
|
3137
|
-
actualRelativePath || relativePath
|
|
2966
|
+
"blame",
|
|
2967
|
+
"--line-porcelain",
|
|
2968
|
+
"--",
|
|
2969
|
+
actualRelativePath || relativePath
|
|
3138
2970
|
], { cwd: root, timeout: this.blameTimeoutMs });
|
|
3139
2971
|
return {
|
|
3140
2972
|
isGitRepo: true,
|
|
3141
2973
|
path: relativePath,
|
|
3142
|
-
lines: parseGitBlamePorcelain(stdout)
|
|
2974
|
+
lines: parseGitBlamePorcelain(stdout)
|
|
3143
2975
|
};
|
|
3144
|
-
} catch (
|
|
3145
|
-
|
|
3146
|
-
|
|
2976
|
+
} catch (caught) {
|
|
2977
|
+
const error = processError(caught);
|
|
2978
|
+
if (error.code === "ENOENT") {
|
|
2979
|
+
throw new WorkspaceFileError("git is not installed", 501);
|
|
3147
2980
|
}
|
|
3148
|
-
if (error.code ===
|
|
3149
|
-
throw new WorkspaceFileError(
|
|
2981
|
+
if (error.code === "ETIMEDOUT" || error.signal === "SIGTERM") {
|
|
2982
|
+
throw new WorkspaceFileError("git blame timed out", 504);
|
|
3150
2983
|
}
|
|
3151
|
-
if (error.stderr && /not a git repository/i.test(error.stderr)) {
|
|
2984
|
+
if (error.stderr && /not a git repository/i.test(String(error.stderr))) {
|
|
3152
2985
|
return {
|
|
3153
2986
|
isGitRepo: false,
|
|
3154
2987
|
path: relativePath,
|
|
3155
|
-
lines: []
|
|
2988
|
+
lines: []
|
|
3156
2989
|
};
|
|
3157
2990
|
}
|
|
3158
|
-
if (error.stderr && /(no such path|no such ref|does not exist in|not in HEAD|no such file)/i.test(error.stderr)) {
|
|
3159
|
-
throw new WorkspaceFileError(
|
|
2991
|
+
if (error.stderr && /(no such path|no such ref|does not exist in|not in HEAD|no such file)/i.test(String(error.stderr))) {
|
|
2992
|
+
throw new WorkspaceFileError("file is not tracked by git", 409, { path: relativePath });
|
|
3160
2993
|
}
|
|
3161
|
-
throw new WorkspaceFileError(error.stderr ||
|
|
2994
|
+
throw new WorkspaceFileError(String(error.stderr || "git blame failed"), 500);
|
|
3162
2995
|
}
|
|
3163
2996
|
}
|
|
3164
|
-
|
|
3165
2997
|
async subscribe(workspaceRoot, callback) {
|
|
3166
|
-
if (this.disposed) return async () => {
|
|
2998
|
+
if (this.disposed) return async () => {
|
|
2999
|
+
};
|
|
3167
3000
|
const lifecycleGeneration = this.watcherLifecycleGeneration;
|
|
3168
3001
|
const root = await this.resolveRoot(workspaceRoot);
|
|
3169
|
-
if (this.disposed || this.watcherLifecycleGeneration !== lifecycleGeneration) return async () => {
|
|
3002
|
+
if (this.disposed || this.watcherLifecycleGeneration !== lifecycleGeneration) return async () => {
|
|
3003
|
+
};
|
|
3170
3004
|
const watchRoot = path.resolve(workspaceRoot);
|
|
3171
3005
|
let record = this.watchers.get(root);
|
|
3172
|
-
|
|
3173
3006
|
if (!record) {
|
|
3174
|
-
const subscribers = new Set();
|
|
3007
|
+
const subscribers = /* @__PURE__ */ new Set();
|
|
3175
3008
|
const generation = this.watcherSubscriptionGeneration + 1;
|
|
3176
3009
|
this.watcherSubscriptionGeneration = generation;
|
|
3177
|
-
|
|
3178
3010
|
const emit = (eventType, absolutePath) => {
|
|
3179
|
-
const relative = relativeFromRoot(watchRoot, absolutePath);
|
|
3011
|
+
const relative = relativeFromRoot(watchRoot, String(absolutePath));
|
|
3180
3012
|
if (shouldHidePath(relative)) return;
|
|
3181
3013
|
this.invalidateGitStatus(root);
|
|
3182
3014
|
subscribers.forEach((subscriber) => {
|
|
3183
3015
|
subscriber({
|
|
3184
3016
|
type: eventType,
|
|
3185
|
-
path: relative
|
|
3017
|
+
path: relative
|
|
3186
3018
|
});
|
|
3187
3019
|
});
|
|
3188
3020
|
};
|
|
3189
|
-
|
|
3190
|
-
|
|
3021
|
+
const emitError = (caught) => {
|
|
3022
|
+
const error = processError(caught);
|
|
3191
3023
|
subscribers.forEach((subscriber) => {
|
|
3192
3024
|
subscriber({
|
|
3193
|
-
type:
|
|
3194
|
-
message: error.message
|
|
3025
|
+
type: "error",
|
|
3026
|
+
message: error.message
|
|
3195
3027
|
});
|
|
3196
3028
|
});
|
|
3197
3029
|
};
|
|
3198
|
-
|
|
3199
3030
|
record = {
|
|
3200
3031
|
watcher: null,
|
|
3201
3032
|
subscribers,
|
|
@@ -3203,62 +3034,56 @@ class WorkspaceFileService {
|
|
|
3203
3034
|
cancelled: false,
|
|
3204
3035
|
cancelInitialization: null,
|
|
3205
3036
|
closePromise: null,
|
|
3206
|
-
ready: null
|
|
3037
|
+
ready: null
|
|
3207
3038
|
};
|
|
3208
3039
|
this.watchers.set(root, record);
|
|
3040
|
+
const initializingRecord = record;
|
|
3209
3041
|
record.ready = (async () => {
|
|
3210
3042
|
const configuredIgnored = this.watchOptions.ignored;
|
|
3211
3043
|
const ignored = (candidatePath) => {
|
|
3212
|
-
const
|
|
3044
|
+
const candidate = String(candidatePath);
|
|
3045
|
+
const relative = path.relative(watchRoot, candidate);
|
|
3213
3046
|
if (shouldHidePath(relative)) return true;
|
|
3214
|
-
if (typeof configuredIgnored ===
|
|
3215
|
-
if (configuredIgnored instanceof RegExp) return configuredIgnored.test(
|
|
3047
|
+
if (typeof configuredIgnored === "function") return Boolean(configuredIgnored(candidate));
|
|
3048
|
+
if (configuredIgnored instanceof RegExp) return configuredIgnored.test(candidate);
|
|
3216
3049
|
if (Array.isArray(configuredIgnored)) {
|
|
3217
|
-
return configuredIgnored.some(pattern => (
|
|
3218
|
-
pattern instanceof RegExp ? pattern.test(candidatePath) : String(candidatePath).includes(String(pattern))
|
|
3219
|
-
));
|
|
3050
|
+
return configuredIgnored.some((pattern) => pattern instanceof RegExp ? pattern.test(candidate) : candidate.includes(String(pattern)));
|
|
3220
3051
|
}
|
|
3221
3052
|
return false;
|
|
3222
3053
|
};
|
|
3223
3054
|
const chokidar = await loadChokidar();
|
|
3224
|
-
if (
|
|
3225
|
-
record.cancelled
|
|
3226
|
-
|| this.watchers.get(root)?.generation !== generation
|
|
3227
|
-
) return false;
|
|
3055
|
+
if (initializingRecord.cancelled || this.watchers.get(root)?.generation !== generation) return false;
|
|
3228
3056
|
const watcher = chokidar.watch(watchRoot, {
|
|
3229
3057
|
ignoreInitial: true,
|
|
3230
3058
|
depth: this.watchDepth,
|
|
3231
3059
|
...this.watchOptions,
|
|
3232
|
-
ignored
|
|
3060
|
+
ignored
|
|
3233
3061
|
});
|
|
3234
|
-
|
|
3235
|
-
|
|
3236
|
-
|
|
3237
|
-
watcher.on(eventType, filePath => emit(eventType, filePath));
|
|
3062
|
+
initializingRecord.watcher = watcher;
|
|
3063
|
+
["add", "change", "unlink", "addDir", "unlinkDir"].forEach((eventType) => {
|
|
3064
|
+
watcher.on(eventType, (filePath) => emit(eventType, filePath));
|
|
3238
3065
|
});
|
|
3239
|
-
watcher.on(
|
|
3240
|
-
const
|
|
3066
|
+
watcher.on("error", emitError);
|
|
3067
|
+
const initialized2 = await new Promise((resolve) => {
|
|
3241
3068
|
let settled = false;
|
|
3242
|
-
const finish = value => {
|
|
3069
|
+
const finish = (value) => {
|
|
3243
3070
|
if (settled) return;
|
|
3244
3071
|
settled = true;
|
|
3245
3072
|
resolve(value);
|
|
3246
3073
|
};
|
|
3247
|
-
watcher.once(
|
|
3248
|
-
|
|
3074
|
+
watcher.once("ready", () => finish(true));
|
|
3075
|
+
initializingRecord.cancelInitialization = () => finish(false);
|
|
3249
3076
|
});
|
|
3250
|
-
|
|
3251
|
-
return
|
|
3252
|
-
&& !record.cancelled
|
|
3253
|
-
&& this.watchers.get(root)?.generation === generation;
|
|
3077
|
+
initializingRecord.cancelInitialization = null;
|
|
3078
|
+
return initialized2 && !initializingRecord.cancelled && this.watchers.get(root)?.generation === generation;
|
|
3254
3079
|
})();
|
|
3255
3080
|
}
|
|
3256
|
-
|
|
3257
3081
|
record.subscribers.add(callback);
|
|
3258
3082
|
let initialized = false;
|
|
3259
3083
|
try {
|
|
3260
|
-
initialized = await record.ready;
|
|
3261
|
-
} catch (
|
|
3084
|
+
initialized = await (record.ready ?? Promise.resolve(false));
|
|
3085
|
+
} catch (caught) {
|
|
3086
|
+
const error = processError(caught);
|
|
3262
3087
|
record.subscribers.delete(callback);
|
|
3263
3088
|
if (record.subscribers.size === 0 && this.watchers.get(root)?.generation === record.generation) {
|
|
3264
3089
|
this.watchers.delete(root);
|
|
@@ -3268,20 +3093,17 @@ class WorkspaceFileService {
|
|
|
3268
3093
|
}
|
|
3269
3094
|
if (!initialized) {
|
|
3270
3095
|
record.subscribers.delete(callback);
|
|
3271
|
-
return async () => {
|
|
3096
|
+
return async () => {
|
|
3097
|
+
};
|
|
3272
3098
|
}
|
|
3273
3099
|
return async () => {
|
|
3274
3100
|
if (!record.subscribers.delete(callback)) return;
|
|
3275
|
-
if (
|
|
3276
|
-
record.subscribers.size === 0
|
|
3277
|
-
&& this.watchers.get(root)?.generation === record.generation
|
|
3278
|
-
) {
|
|
3101
|
+
if (record.subscribers.size === 0 && this.watchers.get(root)?.generation === record.generation) {
|
|
3279
3102
|
this.watchers.delete(root);
|
|
3280
3103
|
await this.closeWorkspaceWatcherRecord(record);
|
|
3281
3104
|
}
|
|
3282
3105
|
};
|
|
3283
3106
|
}
|
|
3284
|
-
|
|
3285
3107
|
async closeWorkspaceWatcherRecord(record) {
|
|
3286
3108
|
if (record.closePromise) return record.closePromise;
|
|
3287
3109
|
record.cancelled = true;
|
|
@@ -3290,40 +3112,39 @@ class WorkspaceFileService {
|
|
|
3290
3112
|
await record.ready?.catch(() => false);
|
|
3291
3113
|
if (!record.watcher) return;
|
|
3292
3114
|
const closeResult = record.watcher.close();
|
|
3293
|
-
if (closeResult && typeof closeResult.then ===
|
|
3115
|
+
if (closeResult && typeof closeResult.then === "function") await closeResult;
|
|
3294
3116
|
})();
|
|
3295
3117
|
return record.closePromise;
|
|
3296
3118
|
}
|
|
3297
|
-
|
|
3298
3119
|
async dispose() {
|
|
3299
3120
|
const watchers = Array.from(this.watchers.values());
|
|
3300
3121
|
this.watchers.clear();
|
|
3301
3122
|
this.disposed = true;
|
|
3302
3123
|
this.watcherLifecycleGeneration += 1;
|
|
3303
3124
|
this.gitStatusCache.clear();
|
|
3304
|
-
await Promise.all(watchers.map(record => this.closeWorkspaceWatcherRecord(record)));
|
|
3125
|
+
await Promise.all(watchers.map((record) => this.closeWorkspaceWatcherRecord(record)));
|
|
3305
3126
|
if (this.ownsCommandRunner) {
|
|
3306
3127
|
this.commandRunner.dispose();
|
|
3307
3128
|
}
|
|
3308
3129
|
}
|
|
3309
3130
|
}
|
|
3310
|
-
|
|
3311
|
-
module.exports = {
|
|
3312
|
-
WorkspaceFileService,
|
|
3313
|
-
WorkspaceFileError,
|
|
3131
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
3132
|
+
0 && (module.exports = {
|
|
3314
3133
|
DEFAULT_MAX_FILE_SIZE,
|
|
3315
3134
|
DEFAULT_MAX_WRITE_SIZE,
|
|
3316
3135
|
DEFAULT_WATCH_DEPTH,
|
|
3317
|
-
|
|
3318
|
-
|
|
3319
|
-
parseUnifiedDiffRows,
|
|
3136
|
+
WorkspaceFileError,
|
|
3137
|
+
WorkspaceFileService,
|
|
3320
3138
|
createAddedFileLineChangesHunk,
|
|
3321
|
-
|
|
3322
|
-
selectUnifiedDiffHunk,
|
|
3323
|
-
resolveCommandRunnerNodePath,
|
|
3139
|
+
isPackagedRuntime,
|
|
3324
3140
|
normalizeGitHistoryLimit,
|
|
3325
3141
|
normalizeGitHistorySkip,
|
|
3142
|
+
parseGitBlamePorcelain,
|
|
3326
3143
|
parseGitHistoryChanges,
|
|
3327
3144
|
parseGitHistoryLog,
|
|
3328
3145
|
parseGitHistoryReferences,
|
|
3329
|
-
|
|
3146
|
+
parseUnifiedDiffHunks,
|
|
3147
|
+
parseUnifiedDiffRows,
|
|
3148
|
+
resolveCommandRunnerNodePath,
|
|
3149
|
+
selectUnifiedDiffHunk
|
|
3150
|
+
});
|