@maudecode/talaria-web 1.14.0 → 1.16.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/_release.json +4 -4
- package/dist/api/automation-router.d.ts +123 -7
- package/dist/api/automation-router.js +5 -0
- package/dist/api/automation-router.js.map +1 -1
- package/dist/api/chat-router.d.ts +728 -55
- package/dist/api/chat-router.js +122 -126
- package/dist/api/chat-router.js.map +1 -1
- package/dist/api/git-router.js +44 -32
- package/dist/api/git-router.js.map +1 -1
- package/dist/api/raw-routes.js +5 -58
- package/dist/api/raw-routes.js.map +1 -1
- package/dist/api/router.d.ts +6 -3
- package/dist/api/router.js +10 -8
- package/dist/api/router.js.map +1 -1
- package/dist/api/sessions-router.d.ts +2583 -297
- package/dist/api/sessions-router.js +92 -47
- package/dist/api/sessions-router.js.map +1 -1
- package/dist/api/settings-router.d.ts +188 -17
- package/dist/api/settings-router.js +75 -26
- package/dist/api/settings-router.js.map +1 -1
- package/dist/api/sse-routes.js +1 -1
- package/dist/api/sse-routes.js.map +1 -1
- package/dist/api/tools-router.d.ts +13 -1
- package/dist/api/tools-router.js +10 -8
- package/dist/api/tools-router.js.map +1 -1
- package/dist/app.d.ts +3620 -343
- package/dist/auth/gate.js +8 -4
- package/dist/auth/gate.js.map +1 -1
- package/dist/auth/oidc.d.ts +6 -2
- package/dist/auth/oidc.js +16 -11
- package/dist/auth/oidc.js.map +1 -1
- package/dist/auth/store.d.ts +14 -2
- package/dist/auth/store.js +26 -1
- package/dist/auth/store.js.map +1 -1
- package/dist/cli/dotenv.d.ts +1 -1
- package/dist/cli/dotenv.js +36 -3
- package/dist/cli/dotenv.js.map +1 -1
- package/dist/config/agent-config.js +20 -15
- package/dist/config/agent-config.js.map +1 -1
- package/dist/http/addresses.d.ts +6 -0
- package/dist/http/addresses.js +26 -0
- package/dist/http/addresses.js.map +1 -1
- package/dist/http/context.d.ts +7 -5
- package/dist/http/context.js.map +1 -1
- package/dist/links.d.ts +26 -0
- package/dist/links.js +60 -0
- package/dist/links.js.map +1 -0
- package/dist/onboarding.d.ts +1 -1
- package/dist/onboarding.js +28 -8
- package/dist/onboarding.js.map +1 -1
- package/dist/profiles/profiles.js +1 -1
- package/dist/profiles/profiles.js.map +1 -1
- package/dist/providers/catalog.d.ts +85 -1
- package/dist/providers/catalog.js +280 -43
- package/dist/providers/catalog.js.map +1 -1
- package/dist/providers/tables.d.ts +3 -1
- package/dist/providers/tables.js +9 -4
- package/dist/providers/tables.js.map +1 -1
- package/dist/redact.js +493 -13
- package/dist/redact.js.map +1 -1
- package/dist/runtime.js +104 -42
- package/dist/runtime.js.map +1 -1
- package/dist/sessions/anchor.d.ts +22 -3
- package/dist/sessions/anchor.js +87 -42
- package/dist/sessions/anchor.js.map +1 -1
- package/dist/sessions/background-tasks.d.ts +140 -0
- package/dist/sessions/background-tasks.js +352 -0
- package/dist/sessions/background-tasks.js.map +1 -0
- package/dist/sessions/background-updates.d.ts +56 -0
- package/dist/sessions/background-updates.js +152 -0
- package/dist/sessions/background-updates.js.map +1 -0
- package/dist/sessions/cli-sessions.d.ts +6 -0
- package/dist/sessions/cli-sessions.js +20 -7
- package/dist/sessions/cli-sessions.js.map +1 -1
- package/dist/sessions/completions.d.ts +17 -1
- package/dist/sessions/completions.js +84 -13
- package/dist/sessions/completions.js.map +1 -1
- package/dist/sessions/compress.d.ts +33 -0
- package/dist/sessions/compress.js +66 -0
- package/dist/sessions/compress.js.map +1 -0
- package/dist/sessions/list.d.ts +22 -4
- package/dist/sessions/list.js +113 -33
- package/dist/sessions/list.js.map +1 -1
- package/dist/sessions/merge.d.ts +81 -7
- package/dist/sessions/merge.js +391 -26
- package/dist/sessions/merge.js.map +1 -1
- package/dist/sessions/pending.d.ts +5 -0
- package/dist/sessions/pending.js +30 -10
- package/dist/sessions/pending.js.map +1 -1
- package/dist/sessions/relay.d.ts +7 -1
- package/dist/sessions/relay.js +17 -4
- package/dist/sessions/relay.js.map +1 -1
- package/dist/sessions/service.d.ts +118 -6
- package/dist/sessions/service.js +564 -65
- package/dist/sessions/service.js.map +1 -1
- package/dist/sessions/session.d.ts +22 -1
- package/dist/sessions/session.js +29 -2
- package/dist/sessions/session.js.map +1 -1
- package/dist/sessions/shares.js +11 -2
- package/dist/sessions/shares.js.map +1 -1
- package/dist/sessions/source-kind.d.ts +22 -0
- package/dist/sessions/source-kind.js +54 -0
- package/dist/sessions/source-kind.js.map +1 -0
- package/dist/sessions/state-db.d.ts +10 -1
- package/dist/sessions/state-db.js +48 -17
- package/dist/sessions/state-db.js.map +1 -1
- package/dist/sessions/store.d.ts +2 -0
- package/dist/sessions/store.js +24 -5
- package/dist/sessions/store.js.map +1 -1
- package/dist/sessions/streams.d.ts +3 -0
- package/dist/sessions/streams.js +0 -5
- package/dist/sessions/streams.js.map +1 -1
- package/dist/sessions/titles.d.ts +4 -0
- package/dist/sessions/titles.js +16 -0
- package/dist/sessions/titles.js.map +1 -1
- package/dist/sessions/turn-context.d.ts +1 -0
- package/dist/sessions/turn-context.js +3 -0
- package/dist/sessions/turn-context.js.map +1 -1
- package/dist/sessions/turn.d.ts +85 -7
- package/dist/sessions/turn.js +518 -170
- package/dist/sessions/turn.js.map +1 -1
- package/dist/settings.d.ts +2 -0
- package/dist/settings.js +13 -0
- package/dist/settings.js.map +1 -1
- package/dist/sidecar/fake.js +2 -0
- package/dist/sidecar/fake.js.map +1 -1
- package/dist/tools/commands.d.ts +15 -0
- package/dist/tools/commands.js +52 -0
- package/dist/tools/commands.js.map +1 -0
- package/dist/tools/crons.d.ts +38 -4
- package/dist/tools/crons.js +323 -36
- package/dist/tools/crons.js.map +1 -1
- package/dist/tools/mcp.js +5 -1
- package/dist/tools/mcp.js.map +1 -1
- package/dist/tools/update-notifications.d.ts +1 -1
- package/dist/tools/update-notifications.js +1 -1
- package/dist/tools/update-notifications.js.map +1 -1
- package/dist/tools/updates.d.ts +8 -3
- package/dist/tools/updates.js +133 -34
- package/dist/tools/updates.js.map +1 -1
- package/dist/workspace/git.d.ts +57 -10
- package/dist/workspace/git.js +302 -195
- package/dist/workspace/git.js.map +1 -1
- package/dist/workspace/media-refs.d.ts +13 -0
- package/dist/workspace/media-refs.js +165 -0
- package/dist/workspace/media-refs.js.map +1 -0
- package/dist/workspace/media.d.ts +19 -0
- package/dist/workspace/media.js +61 -2
- package/dist/workspace/media.js.map +1 -1
- package/dist/workspace/workspaces.d.ts +7 -1
- package/dist/workspace/workspaces.js +30 -21
- package/dist/workspace/workspaces.js.map +1 -1
- package/dist/workspace/worktrees.d.ts +2 -4
- package/dist/workspace/worktrees.js +28 -33
- package/dist/workspace/worktrees.js.map +1 -1
- package/package.json +4 -2
- package/sidecar/scripts/replay_sidecar.py +3 -1
- package/sidecar/talaria_sidecar/__init__.py +1 -1
- package/sidecar/talaria_sidecar/home.py +59 -2
- package/sidecar/talaria_sidecar/methods/__init__.py +2 -2
- package/sidecar/talaria_sidecar/methods/aux.py +19 -3
- package/sidecar/talaria_sidecar/methods/chat.py +360 -27
- package/sidecar/talaria_sidecar/methods/commands.py +46 -31
- package/sidecar/talaria_sidecar/methods/config.py +14 -10
- package/sidecar/talaria_sidecar/methods/cron.py +22 -14
- package/sidecar/talaria_sidecar/methods/oauth.py +439 -0
- package/sidecar/talaria_sidecar/methods/process.py +318 -0
- package/sidecar/talaria_sidecar/methods/profiles.py +5 -4
- package/sidecar/talaria_sidecar/methods/providers.py +161 -1
- package/sidecar/talaria_sidecar/methods/runtime.py +2 -0
- package/sidecar/talaria_sidecar/methods/text.py +1 -20
- package/sidecar/talaria_sidecar/methods/usage.py +7 -1
- package/static/dist/FILES.txt +124 -119
- package/static/dist/assets/AppShell-ef3bhu32.js +1 -0
- package/static/dist/assets/Button-nx37f4u1.js +1 -0
- package/static/dist/assets/ChatPage-cdcpykcg.js +25 -0
- package/static/dist/assets/Dialog-mec42hvb.js +1 -0
- package/static/dist/assets/HubRoute-ecb65zpr.js +2 -0
- package/static/dist/assets/{Markdown-emii55o0.js → Markdown-jqrx5es0.js} +125 -125
- package/static/dist/assets/RightPanel-d1lgvk4s.js +1 -0
- package/static/dist/assets/Select-h4ivl9qf.js +1 -0
- package/static/dist/assets/SessionListPanel-ewib046b.js +1 -0
- package/static/dist/assets/SettingsLayout-lx654cup.js +1 -0
- package/static/dist/assets/States-ehsb6lzo.js +1 -0
- package/static/dist/assets/Toaster-mch1pa3t.js +1 -0
- package/static/dist/assets/_app-m8d961ef.js +1 -0
- package/static/dist/assets/_app.ext._extensionId-mp33hbzi.js +1 -0
- package/static/dist/assets/_app.index-dk6zp7e0.js +1 -0
- package/static/dist/assets/_app.insights-jt2fk4i1.js +1 -0
- package/static/dist/assets/_app.kanban-if4pubeu.js +1 -0
- package/static/dist/assets/_app.logs-mfaw9m6g.js +1 -0
- package/static/dist/assets/_app.memory-h3eaarb6.js +1 -0
- package/static/dist/assets/_app.profiles-ienktovs.js +1 -0
- package/static/dist/assets/{_app.session._sessionId-m6nx9g8t.js → _app.session._sessionId-hh27wwu2.js} +1 -1
- package/static/dist/assets/_app.settings-dpoe0bzf.js +1 -0
- package/static/dist/assets/_app.settings._section-l468ynag.js +3 -0
- package/static/dist/assets/_app.skills-kcbncc8i.js +1 -0
- package/static/dist/assets/_app.tasks-occdrqhw.js +2 -0
- package/static/dist/assets/_app.todos-nnke2rcc.js +1 -0
- package/static/dist/assets/_app.workspaces-cdlatvwj.js +1 -0
- package/static/dist/assets/{abnfDiagram-VCTEODGH-kejgxpil.js → abnfDiagram-VCTEODGH-oity9y4o.js} +1 -1
- package/static/dist/assets/architecture-7GRP2DOG-hj11333z.js +1 -0
- package/static/dist/assets/architectureDiagram-5GKGNRK7-ceaepnyr.js +36 -0
- package/static/dist/assets/back-eu64w89w.js +1 -0
- package/static/dist/assets/{blockDiagram-I7D4REHJ-l1466a7j.js → blockDiagram-I7D4REHJ-n79st4pj.js} +1 -1
- package/static/dist/assets/{c4Diagram-7LVT6UL2-gg3aq3sr.js → c4Diagram-7LVT6UL2-mi73akzk.js} +1 -1
- package/static/dist/assets/cancel-mzvkdjg8.js +1 -0
- package/static/dist/assets/channel-mbvmf2vv.js +1 -0
- package/static/dist/assets/{chunk-4HAMMTFA-oalgz5l8.js → chunk-4HAMMTFA-laat0g1q.js} +1 -1
- package/static/dist/assets/{chunk-75Z2AOVW-babmzhdg.js → chunk-75Z2AOVW-0psy1tr2.js} +1 -1
- package/static/dist/assets/{chunk-DU6HZSFF-lagu350c.js → chunk-DU6HZSFF-lw8uox8x.js} +3 -3
- package/static/dist/assets/{chunk-F27PBJKO-gjh2tqif.js → chunk-F27PBJKO-erassib3.js} +1 -1
- package/static/dist/assets/{chunk-GMAD6QVW-ipdgja5c.js → chunk-GMAD6QVW-fz7rx619.js} +1 -1
- package/static/dist/assets/{chunk-GVQU2GXP-j8wkn1at.js → chunk-GVQU2GXP-gdb0epb2.js} +1 -1
- package/static/dist/assets/{chunk-IMKFNOWR-k00laqyk.js → chunk-IMKFNOWR-ea8t26qw.js} +1 -1
- package/static/dist/assets/{chunk-L3NEJ4N5-fju6xqj4.js → chunk-L3NEJ4N5-mb1gu9kz.js} +1 -1
- package/static/dist/assets/{chunk-OSK3NFVY-bi6widqt.js → chunk-OSK3NFVY-eovmccvy.js} +1 -1
- package/static/dist/assets/{chunk-P2QGCYS3-czr4tebv.js → chunk-P2QGCYS3-bo55zjo5.js} +1 -1
- package/static/dist/assets/{chunk-POPQ4Y6H-ktdhn67w.js → chunk-POPQ4Y6H-ja7ohe4h.js} +1 -1
- package/static/dist/assets/{chunk-PWAF6VOD-ii4dsu0d.js → chunk-PWAF6VOD-e30g9nus.js} +1 -1
- package/static/dist/assets/{chunk-SHT3W25Y-fpwk3xxp.js → chunk-SHT3W25Y-gbb798f7.js} +3 -3
- package/static/dist/assets/{chunk-SVP7TREG-k0jrvhei.js → chunk-SVP7TREG-h2iw8urm.js} +1 -1
- package/static/dist/assets/{chunk-TICWLB2K-nkqv9ot0.js → chunk-TICWLB2K-fd2cottz.js} +1 -1
- package/static/dist/assets/classDiagram-ZZMXUADV-o8r05k0n.js +1 -0
- package/static/dist/assets/classDiagram-v2-VYDZK3BY-o8r05k0n.js +1 -0
- package/static/dist/assets/{copied-gpchexqu.js → copied-h65fc3cx.js} +1 -1
- package/static/dist/assets/copy-e6977uwq.js +1 -0
- package/static/dist/assets/{cynefin-OW5HDTMX-lqxt8rs3.js → cynefin-OW5HDTMX-lbltyl5j.js} +1 -1
- package/static/dist/assets/{cynefinDiagram-5FMLGOSQ-j9eqovf5.js → cynefinDiagram-5FMLGOSQ-lxhsqq4a.js} +1 -1
- package/static/dist/assets/{dagre-GXQ25YYZ-m9agobjm.js → dagre-GXQ25YYZ-gn4nnaln.js} +4 -4
- package/static/dist/assets/{diagram-S7CK7UJ4-itodc50c.js → diagram-S7CK7UJ4-pa3ij6q7.js} +3 -3
- package/static/dist/assets/{diagram-UQ7AKVKN-n1h2t4ah.js → diagram-UQ7AKVKN-zbipyz57.js} +1 -1
- package/static/dist/assets/{diagram-VSXAHHWV-lvtvm9bj.js → diagram-VSXAHHWV-d1kbpzgf.js} +1 -1
- package/static/dist/assets/{diagram-VX7I27RA-cgas58sy.js → diagram-VX7I27RA-i3ty14xl.js} +1 -1
- package/static/dist/assets/{diagram-Z3DM3KII-d0aeps1q.js → diagram-Z3DM3KII-m8xefqa7.js} +1 -1
- package/static/dist/assets/{ebnfDiagram-PWID7BFC-md345fyq.js → ebnfDiagram-PWID7BFC-nz7wj05l.js} +1 -1
- package/static/dist/assets/endpoints-k7yf25m0.js +64 -0
- package/static/dist/assets/{erDiagram-RLTQ6QDP-i4rua63b.js → erDiagram-RLTQ6QDP-fa41ib83.js} +1 -1
- package/static/dist/assets/eventmodeling-NTZA5JFV-kpbq09bx.js +1 -0
- package/static/dist/assets/flowDiagram-HODETNUW-lcz0kx14.js +1 -0
- package/static/dist/assets/{ganttDiagram-EL5Y4UJY-geqx7zgl.js → ganttDiagram-EL5Y4UJY-n7zq01qu.js} +1 -1
- package/static/dist/assets/{gitGraph-4MIJSDKK-leoq0z8t.js → gitGraph-4MIJSDKK-c89emb07.js} +1 -1
- package/static/dist/assets/{gitGraphDiagram-WWUBYQGX-llrlxfha.js → gitGraphDiagram-WWUBYQGX-cz7cgr6w.js} +1 -1
- package/static/dist/assets/{highlighted-body-KPVGNVTW-kq2cnw6q.js → highlighted-body-KPVGNVTW-d9bapmni.js} +1 -1
- package/static/dist/assets/{index-f9bzglkn.js → index-h00usru2.js} +3 -3
- package/static/dist/assets/index-n73ijkv2.css +2 -0
- package/static/dist/assets/{info-A6RAGUB7-lkly4xj6.js → info-A6RAGUB7-ndddhhhh.js} +1 -1
- package/static/dist/assets/infoDiagram-27XIBGKW-ccxigcpf.js +2 -0
- package/static/dist/assets/{ishikawaDiagram-5VMMS53U-hq6jf374.js → ishikawaDiagram-5VMMS53U-le0z9vtc.js} +1 -1
- package/static/dist/assets/{journeyDiagram-3NMN7TZE-is03ntb0.js → journeyDiagram-3NMN7TZE-ivsmb26o.js} +1 -1
- package/static/dist/assets/{kanban-definition-UXKFOSKX-jmerizqb.js → kanban-definition-UXKFOSKX-h6n2awt0.js} +1 -1
- package/static/dist/assets/lazyRouteComponent-m1rtyk4t.js +1 -0
- package/static/dist/assets/{line-laaa8h99.js → line-dmzlfe0u.js} +1 -1
- package/static/dist/assets/link-budeg5t9.js +1 -0
- package/static/dist/assets/login-kjhasvp8.js +1 -0
- package/static/dist/assets/mermaid-HWGCJPDP-jb5d211j.js +1 -0
- package/static/dist/assets/{mermaid-parser.core-ix4junux.js → mermaid-parser.core-gwyd5bnq.js} +3 -3
- package/static/dist/assets/{mindmap-definition-YA3MSWOX-o0e0adl1.js → mindmap-definition-YA3MSWOX-l68gg5h7.js} +1 -1
- package/static/dist/assets/onboarding-cdfrk0pj.js +1 -0
- package/static/dist/assets/{packet-AYTQ26CC-kl8pwsyg.js → packet-AYTQ26CC-gpv06065.js} +1 -1
- package/static/dist/assets/{pegDiagram-XKGWAZYB-bbqn9jo4.js → pegDiagram-XKGWAZYB-e52vs23u.js} +1 -1
- package/static/dist/assets/{pie-WAS4IAKB-cpyo8bkr.js → pie-WAS4IAKB-l6m7nz5s.js} +1 -1
- package/static/dist/assets/{pieDiagram-E7YTZNPT-pelug34f.js → pieDiagram-E7YTZNPT-b4is36bq.js} +3 -3
- package/static/dist/assets/{quadrantDiagram-AXDQQJYC-jds4jmr5.js → quadrantDiagram-AXDQQJYC-m8vt2788.js} +1 -1
- package/static/dist/assets/{radar-RG4KPBEZ-e02oxnvd.js → radar-RG4KPBEZ-nxbrhiov.js} +1 -1
- package/static/dist/assets/{railroad-74A4TZTK-kz3delwo.js → railroad-74A4TZTK-222mu9hi.js} +1 -1
- package/static/dist/assets/railroad-abnf-HS5TGJTU-kdvgl6x8.js +1 -0
- package/static/dist/assets/railroad-ebnf-LZEXJU2U-itiqslby.js +1 -0
- package/static/dist/assets/railroad-peg-WCYAUIDC-go7kov78.js +1 -0
- package/static/dist/assets/{railroadDiagram-O6MQD6OU-jgroj2ew.js → railroadDiagram-O6MQD6OU-o8lldfzv.js} +1 -1
- package/static/dist/assets/{refresh-cw-k7yxjdn3.js → refresh-cw-kbcje1uy.js} +1 -1
- package/static/dist/assets/{requirementDiagram-BXWQKSXE-m4az5e0j.js → requirementDiagram-BXWQKSXE-ovyq38kv.js} +1 -1
- package/static/dist/assets/{sankeyDiagram-P5KCCOFB-vlryqiv3.js → sankeyDiagram-P5KCCOFB-ou7lznor.js} +1 -1
- package/static/dist/assets/{saved-brdfjjnw.js → saved-bazmcup9.js} +1 -1
- package/static/dist/assets/search-jen74z4u.js +1 -0
- package/static/dist/assets/{sequenceDiagram-WJ2MYXX4-fr8hrpql.js → sequenceDiagram-WJ2MYXX4-ovckdzf2.js} +1 -1
- package/static/dist/assets/{settings_section_extensions_title-l0zodj0b.js → settings_section_extensions_title-ertwwhlx.js} +1 -1
- package/static/dist/assets/share._token-dan9hgvj.js +2 -0
- package/static/dist/assets/{sliders-horizontal-gcgscv8r.js → sliders-horizontal-f9cb3lh3.js} +1 -1
- package/static/dist/assets/{stateDiagram-D77RDMKH-bg548iyr.js → stateDiagram-D77RDMKH-fcrexkkv.js} +1 -1
- package/static/dist/assets/stateDiagram-v2-MP3YSRHH-lcfraua2.js +1 -0
- package/static/dist/assets/{swimlanes-42K2YHIH-hk5z3efh.js → swimlanes-42K2YHIH-f7usu8fw.js} +1 -1
- package/static/dist/assets/swimlanesDiagram-VR7AAH4N-ogmanni0.js +8 -0
- package/static/dist/assets/{timeline-definition-24CTP7MA-olvknr62.js → timeline-definition-24CTP7MA-gp1hvz9f.js} +1 -1
- package/static/dist/assets/todoStore-eqtid4r4.js +1 -0
- package/static/dist/assets/{treeView-Q6P3EWNA-ovvjsxtf.js → treeView-Q6P3EWNA-gb9gf11u.js} +1 -1
- package/static/dist/assets/{treemap-WGGIJYW6-ggkipj59.js → treemap-WGGIJYW6-nuvfnvvi.js} +1 -1
- package/static/dist/assets/{untitled-c8ydw0bu.js → untitled-htelhc68.js} +1 -1
- package/static/dist/assets/useForm-h2t0u14e.js +1 -0
- package/static/dist/assets/useLocale-hcsb84pj.js +1 -0
- package/static/dist/assets/useNavigate-nwy70ztj.js +1 -0
- package/static/dist/assets/{useParams-katwvzil.js → useParams-dqd64jv7.js} +1 -1
- package/static/dist/assets/{useSearch-c70sgau1.js → useSearch-j30pvsy7.js} +1 -1
- package/static/dist/assets/{vennDiagram-4TSXK5OY-hzld7zt7.js → vennDiagram-4TSXK5OY-fzchbzaj.js} +1 -1
- package/static/dist/assets/{wardley-WFR3VGLG-hxkeoms8.js → wardley-WFR3VGLG-m47mhfbh.js} +1 -1
- package/static/dist/assets/{wardleyDiagram-VM6X3IG4-ljh90mb1.js → wardleyDiagram-VM6X3IG4-odg6ct1z.js} +2 -2
- package/static/dist/assets/{xychartDiagram-S5SC5T6Z-mnuaes03.js → xychartDiagram-S5SC5T6Z-mrhms5um.js} +1 -1
- package/static/dist/index.html +1 -1
- package/static/dist/sw.js +1 -1
- package/static/dist/assets/AppShell-haqi9qzu.js +0 -1
- package/static/dist/assets/ChatPage-hjyu2g64.js +0 -22
- package/static/dist/assets/HubRoute-gkd8o1vg.js +0 -2
- package/static/dist/assets/RightPanel-o73y98dn.js +0 -1
- package/static/dist/assets/Select-jld3yeni.js +0 -1
- package/static/dist/assets/SessionListPanel-d3qx4ric.js +0 -1
- package/static/dist/assets/SettingsLayout-lnn2wo2s.js +0 -1
- package/static/dist/assets/States-e5hv6gdh.js +0 -1
- package/static/dist/assets/Toaster-c4jq1i26.js +0 -1
- package/static/dist/assets/_app-bnip2pjg.js +0 -1
- package/static/dist/assets/_app.ext._extensionId-egptzrfy.js +0 -1
- package/static/dist/assets/_app.index-vdz13ul5.js +0 -1
- package/static/dist/assets/_app.insights-lgjshsba.js +0 -1
- package/static/dist/assets/_app.kanban-c18cjrpz.js +0 -1
- package/static/dist/assets/_app.logs-ezhojgeg.js +0 -1
- package/static/dist/assets/_app.memory-i00paisv.js +0 -1
- package/static/dist/assets/_app.profiles-kd02bwjx.js +0 -1
- package/static/dist/assets/_app.settings-giaudkuk.js +0 -1
- package/static/dist/assets/_app.settings._section-k4qtlexk.js +0 -1
- package/static/dist/assets/_app.skills-bby2uvl8.js +0 -1
- package/static/dist/assets/_app.tasks-1kz9bosm.js +0 -2
- package/static/dist/assets/_app.todos-izjdbkm2.js +0 -1
- package/static/dist/assets/_app.workspaces-lfh2kw7s.js +0 -1
- package/static/dist/assets/architecture-7GRP2DOG-b4u9z1w3.js +0 -1
- package/static/dist/assets/architectureDiagram-5GKGNRK7-hqnf84fc.js +0 -36
- package/static/dist/assets/channel-o77ioktg.js +0 -1
- package/static/dist/assets/classDiagram-ZZMXUADV-f5rvod26.js +0 -1
- package/static/dist/assets/classDiagram-v2-VYDZK3BY-f5rvod26.js +0 -1
- package/static/dist/assets/endpoints-e58saih3.js +0 -9
- package/static/dist/assets/eventmodeling-NTZA5JFV-bn3fjsaq.js +0 -1
- package/static/dist/assets/flowDiagram-HODETNUW-li70d120.js +0 -1
- package/static/dist/assets/index-gkh7kum4.css +0 -2
- package/static/dist/assets/infoDiagram-27XIBGKW-oo7kdyte.js +0 -2
- package/static/dist/assets/lazyRouteComponent-b1h2ykmb.js +0 -1
- package/static/dist/assets/link-d8f0788e.js +0 -1
- package/static/dist/assets/login-llkohrcg.js +0 -1
- package/static/dist/assets/mermaid-HWGCJPDP-id0ffk2r.js +0 -1
- package/static/dist/assets/onboarding-lx0d6b38.js +0 -1
- package/static/dist/assets/railroad-abnf-HS5TGJTU-l13ped2z.js +0 -1
- package/static/dist/assets/railroad-ebnf-LZEXJU2U-c7rg3g4g.js +0 -1
- package/static/dist/assets/railroad-peg-WCYAUIDC-od0b8niq.js +0 -1
- package/static/dist/assets/search-gt2ueyvg.js +0 -1
- package/static/dist/assets/share._token-oyjv046r.js +0 -2
- package/static/dist/assets/stateDiagram-v2-MP3YSRHH-j5v40qih.js +0 -1
- package/static/dist/assets/swimlanesDiagram-VR7AAH4N-grzefo9c.js +0 -8
- package/static/dist/assets/tabBuild-idliyw9f.js +0 -56
- package/static/dist/assets/todoStore-j4c2xq0b.js +0 -1
- package/static/dist/assets/useForm-kd759sjk.js +0 -1
- package/static/dist/assets/useLocale-hds4xmja.js +0 -1
package/dist/workspace/git.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* pathspecs, and keeps every Git subprocess shell-free, env-scrubbed, hardened
|
|
7
7
|
* against repo-local config, and bounded by a timeout.
|
|
8
8
|
*/
|
|
9
|
-
import {
|
|
9
|
+
import { execFile } from 'node:child_process';
|
|
10
10
|
import { closeSync, existsSync, fstatSync, lstatSync, mkdtempSync, mkdirSync, readFileSync, realpathSync, rmSync, statSync, writeFileSync } from 'node:fs';
|
|
11
11
|
import { tmpdir } from 'node:os';
|
|
12
12
|
import { dirname, join, relative, resolve, sep } from 'node:path';
|
|
@@ -21,6 +21,8 @@ export const STATUS_CACHE_LIMIT = 32;
|
|
|
21
21
|
export const DIFF_SIZE_LIMIT = 512 * 1024;
|
|
22
22
|
export const COMMIT_MESSAGE_DIFF_LIMIT = 64 * 1024;
|
|
23
23
|
export const WORKSPACE_GIT_DESTRUCTIVE_ENV = 'HERMES_WEBUI_WORKSPACE_GIT_DESTRUCTIVE';
|
|
24
|
+
export const WORKSPACE_BUSY_MESSAGE = 'A Git operation is running in this workspace.';
|
|
25
|
+
export const WORKSPACE_WRITE_BUSY_MESSAGE = 'A file operation is running in this workspace.';
|
|
24
26
|
const GIT_ENV_SCRUB_KEYS = ['GIT_DIR', 'GIT_WORK_TREE', 'GIT_CONFIG_GLOBAL', 'GIT_CONFIG_SYSTEM', 'GIT_CONFIG_COUNT', 'GIT_CONFIG_PARAMETERS', 'GIT_ASKPASS', 'SSH_ASKPASS', 'GIT_SSH', 'GIT_SSH_COMMAND'];
|
|
25
27
|
const GIT_ENV_SCRUB_PREFIXES = ['GIT_CONFIG_KEY_', 'GIT_CONFIG_VALUE_'];
|
|
26
28
|
const BRANCH_SWITCH_STASH_PREFIX = 'hermes-webui branch switch';
|
|
@@ -51,6 +53,67 @@ export class GitWorkspaceError extends Error {
|
|
|
51
53
|
this.name = 'GitWorkspaceError';
|
|
52
54
|
}
|
|
53
55
|
}
|
|
56
|
+
/**
|
|
57
|
+
* Run `git` without blocking the event loop, resolving like `spawnSync`: the exit status (null when a signal ended it),
|
|
58
|
+
* plus the `error` that prevented a normal run (`ETIMEDOUT` when the timeout killed it). Without `env` the child
|
|
59
|
+
* inherits the server's environment.
|
|
60
|
+
*/
|
|
61
|
+
export function spawnGit(cwd, argv, timeoutMs, env) {
|
|
62
|
+
return new Promise((done) => {
|
|
63
|
+
execFile('git', argv, { cwd, ...(env ? { env } : {}), encoding: 'utf8', timeout: timeoutMs, maxBuffer: 64 * 1024 * 1024, windowsHide: true }, (error, stdout, stderr) => {
|
|
64
|
+
if (!error)
|
|
65
|
+
done({ status: 0, stdout, stderr });
|
|
66
|
+
else if (typeof error.code === 'number')
|
|
67
|
+
done({ status: error.code, stdout, stderr });
|
|
68
|
+
else if (error.code == null)
|
|
69
|
+
done({ status: null, stdout, stderr, ...(error.killed ? { error: Object.assign(new Error('git timed out'), { code: 'ETIMEDOUT' }) } : {}) });
|
|
70
|
+
else
|
|
71
|
+
done({ status: null, stdout, stderr, error: error });
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* The nearest directory at or above `path` holding a `.git` entry (a repository, linked worktree, or submodule root), the
|
|
77
|
+
* root Git itself would discover, found synchronously so a caller can hold it before its first await.
|
|
78
|
+
*/
|
|
79
|
+
export function enclosingRepoRoot(path) {
|
|
80
|
+
let dir = resolvePathLikePython(path);
|
|
81
|
+
for (;;) {
|
|
82
|
+
if (existsSync(join(dir, '.git')))
|
|
83
|
+
return dir;
|
|
84
|
+
const parent = dirname(dir);
|
|
85
|
+
if (parent === dir)
|
|
86
|
+
return null;
|
|
87
|
+
dir = parent;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
/** Whether two paths are the same directory or one contains the other. */
|
|
91
|
+
export function pathsOverlap(a, b) {
|
|
92
|
+
const x = resolvePathLikePython(a);
|
|
93
|
+
const y = resolvePathLikePython(b);
|
|
94
|
+
return x === y || isWithin(x, y) || isWithin(y, x);
|
|
95
|
+
}
|
|
96
|
+
function overlapsAny(held, path) {
|
|
97
|
+
for (const p of held.keys())
|
|
98
|
+
if (pathsOverlap(p, path))
|
|
99
|
+
return true;
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
/** Count `path` in `held` until `fn` settles; the count is taken before `fn` runs. */
|
|
103
|
+
async function holding(held, path, fn) {
|
|
104
|
+
const key = resolvePathLikePython(path);
|
|
105
|
+
held.set(key, (held.get(key) ?? 0) + 1);
|
|
106
|
+
try {
|
|
107
|
+
return await fn();
|
|
108
|
+
}
|
|
109
|
+
finally {
|
|
110
|
+
const left = (held.get(key) ?? 1) - 1;
|
|
111
|
+
if (left)
|
|
112
|
+
held.set(key, left);
|
|
113
|
+
else
|
|
114
|
+
held.delete(key);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
54
117
|
export function classifyGitError(message, args = []) {
|
|
55
118
|
const text = message.toLowerCase();
|
|
56
119
|
const joined = args.join(' ').toLowerCase();
|
|
@@ -112,9 +175,38 @@ export class GitRunner {
|
|
|
112
175
|
locks = new Map();
|
|
113
176
|
statusCache = new Map();
|
|
114
177
|
generations = new Map();
|
|
178
|
+
/** Workspaces a working-tree mutation or worktree removal is changing; runs, terminals, and file writes stay out. */
|
|
179
|
+
busy = new Map();
|
|
180
|
+
/** Workspaces an asynchronous writer (worktree creation) is changing; working-tree mutations stay out. */
|
|
181
|
+
writing = new Map();
|
|
115
182
|
constructor(deps) {
|
|
116
183
|
this.deps = deps;
|
|
117
184
|
}
|
|
185
|
+
/**
|
|
186
|
+
* Hold `path` busy until `fn` settles. The hold is taken before `fn` runs, so checks `fn` makes synchronously (no run
|
|
187
|
+
* active) and admission's `workspaceBusy` refusal leave no gap for a chat run, terminal, or file write in between.
|
|
188
|
+
*/
|
|
189
|
+
holdWorkspace(path, fn) {
|
|
190
|
+
return holding(this.busy, path, fn);
|
|
191
|
+
}
|
|
192
|
+
/** Whether `path` overlaps a held workspace (the same directory, inside it, or containing it). */
|
|
193
|
+
workspaceBusy(path) {
|
|
194
|
+
return overlapsAny(this.busy, path);
|
|
195
|
+
}
|
|
196
|
+
/** Run an asynchronous workspace writer at `path`: refused while Git holds it, and Git mutations refuse meanwhile. */
|
|
197
|
+
holdWrite(path, fn) {
|
|
198
|
+
if (this.workspaceBusy(path))
|
|
199
|
+
throw new GitWorkspaceError(WORKSPACE_BUSY_MESSAGE, 'operation_in_progress');
|
|
200
|
+
return holding(this.writing, path, fn);
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Worktree removal: serialized with Git mutations in the main checkout and in the worktree (the same per-repo locks),
|
|
204
|
+
* and held busy so no run, terminal, or file write starts there. `fn`'s synchronous lock checks run inside the hold.
|
|
205
|
+
*/
|
|
206
|
+
async removeWorktreeExclusive(worktree, repoRoot, fn) {
|
|
207
|
+
const inWorktree = () => this.withLock(worktree, () => this.holdWorkspace(worktree, fn));
|
|
208
|
+
return repoRoot ? await this.withLock(repoRoot, inWorktree) : await inWorktree();
|
|
209
|
+
}
|
|
118
210
|
destructiveEnabled() {
|
|
119
211
|
return ['1', 'true', 'yes', 'on'].includes((this.deps.env[WORKSPACE_GIT_DESTRUCTIVE_ENV] ?? '').trim().toLowerCase());
|
|
120
212
|
}
|
|
@@ -136,11 +228,8 @@ export class GitRunner {
|
|
|
136
228
|
return this.deps.now ? this.deps.now() * 1000 : Date.now();
|
|
137
229
|
}
|
|
138
230
|
// ── subprocess ───────────────────────────────────────────────────────────
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
}
|
|
142
|
-
configNamesForScope(scope, cwd, env, pattern, nameRe, ignoreUnsupported = false) {
|
|
143
|
-
const result = this.rawGit(cwd, ['config', '--includes', scope, '--name-only', '--get-regexp', pattern], env, GIT_TIMEOUT_MS);
|
|
231
|
+
async configNamesForScope(scope, cwd, env, pattern, nameRe, ignoreUnsupported = false) {
|
|
232
|
+
const result = await spawnGit(cwd, ['config', '--includes', scope, '--name-only', '--get-regexp', pattern], GIT_TIMEOUT_MS, env);
|
|
144
233
|
const names = new Set();
|
|
145
234
|
if (result.status !== 0 && result.status !== 1) {
|
|
146
235
|
if (ignoreUnsupported)
|
|
@@ -155,28 +244,28 @@ export class GitRunner {
|
|
|
155
244
|
}
|
|
156
245
|
return names;
|
|
157
246
|
}
|
|
158
|
-
namesLocalAndWorktree(cwd, env, pattern, nameRe) {
|
|
159
|
-
const names = this.configNamesForScope('--local', cwd, env, pattern, nameRe);
|
|
160
|
-
for (const n of this.configNamesForScope('--worktree', cwd, env, pattern, nameRe, true))
|
|
247
|
+
async namesLocalAndWorktree(cwd, env, pattern, nameRe) {
|
|
248
|
+
const names = await this.configNamesForScope('--local', cwd, env, pattern, nameRe);
|
|
249
|
+
for (const n of await this.configNamesForScope('--worktree', cwd, env, pattern, nameRe, true))
|
|
161
250
|
names.add(n);
|
|
162
251
|
return names;
|
|
163
252
|
}
|
|
164
|
-
filterNames(cwd, env) {
|
|
165
|
-
return this.namesLocalAndWorktree(cwd, env, String.raw `^filter\..*\.(clean|smudge|process|required)$`, /^filter\.(.+)\.(clean|smudge|process|required)$/);
|
|
253
|
+
async filterNames(cwd, env) {
|
|
254
|
+
return await this.namesLocalAndWorktree(cwd, env, String.raw `^filter\..*\.(clean|smudge|process|required)$`, /^filter\.(.+)\.(clean|smudge|process|required)$/);
|
|
166
255
|
}
|
|
167
|
-
mergeDriverNames(cwd, env) {
|
|
168
|
-
return this.namesLocalAndWorktree(cwd, env, String.raw `^merge\..*\.driver$`, /^merge\.(.+)\.driver$/);
|
|
256
|
+
async mergeDriverNames(cwd, env) {
|
|
257
|
+
return await this.namesLocalAndWorktree(cwd, env, String.raw `^merge\..*\.driver$`, /^merge\.(.+)\.driver$/);
|
|
169
258
|
}
|
|
170
|
-
remoteHelperNames(cwd, env) {
|
|
171
|
-
return this.namesLocalAndWorktree(cwd, env, String.raw `^remote\..*\.(uploadpack|receivepack)$`, /^remote\.(.+)\.(uploadpack|receivepack)$/);
|
|
259
|
+
async remoteHelperNames(cwd, env) {
|
|
260
|
+
return await this.namesLocalAndWorktree(cwd, env, String.raw `^remote\..*\.(uploadpack|receivepack)$`, /^remote\.(.+)\.(uploadpack|receivepack)$/);
|
|
172
261
|
}
|
|
173
262
|
static legal(name) {
|
|
174
263
|
return !name.includes('\n') && !name.includes('\0');
|
|
175
264
|
}
|
|
176
|
-
hasRepoLocalFilters(cwd) {
|
|
177
|
-
return this.filterNames(cwd, this.cleanEnv()).size > 0;
|
|
265
|
+
async hasRepoLocalFilters(cwd) {
|
|
266
|
+
return (await this.filterNames(cwd, this.cleanEnv())).size > 0;
|
|
178
267
|
}
|
|
179
|
-
run(ctxOrCwd, args, opts = {}) {
|
|
268
|
+
async run(ctxOrCwd, args, opts = {}) {
|
|
180
269
|
const cwd = typeof ctxOrCwd === 'string' ? ctxOrCwd : ctxOrCwd.repoRoot;
|
|
181
270
|
const runEnv = this.cleanEnv(opts.env);
|
|
182
271
|
const effectiveDestructive = Boolean(opts.destructive) && this.destructiveEnabled();
|
|
@@ -191,21 +280,21 @@ export class GitRunner {
|
|
|
191
280
|
writeFileSync(attributesFile, '');
|
|
192
281
|
}
|
|
193
282
|
if (opts.disableFilterAttributes || opts.neutralizeFilterPrograms) {
|
|
194
|
-
for (const name of [...this.filterNames(cwd, runEnv)].sort()) {
|
|
283
|
+
for (const name of [...await this.filterNames(cwd, runEnv)].sort()) {
|
|
195
284
|
if (!GitRunner.legal(name))
|
|
196
285
|
continue;
|
|
197
286
|
extraConfigs.push([`filter.${name}.clean`, 'cat'], [`filter.${name}.smudge`, 'cat'], [`filter.${name}.process`, ''], [`filter.${name}.required`, 'false']);
|
|
198
287
|
}
|
|
199
288
|
}
|
|
200
289
|
if (effectiveDestructive) {
|
|
201
|
-
for (const name of [...this.mergeDriverNames(cwd, runEnv)].sort()) {
|
|
290
|
+
for (const name of [...await this.mergeDriverNames(cwd, runEnv)].sort()) {
|
|
202
291
|
if (!GitRunner.legal(name))
|
|
203
292
|
continue;
|
|
204
293
|
extraConfigs.push([`merge.${name}.driver`, 'git merge-file "%A" "%O" "%B"']);
|
|
205
294
|
}
|
|
206
295
|
}
|
|
207
296
|
if (effectiveDestructive || opts.neutralizeRemoteHelpers) {
|
|
208
|
-
const names = [...this.remoteHelperNames(cwd, runEnv)].sort();
|
|
297
|
+
const names = [...await this.remoteHelperNames(cwd, runEnv)].sort();
|
|
209
298
|
for (const name of names) {
|
|
210
299
|
if (!GitRunner.legal(name))
|
|
211
300
|
continue;
|
|
@@ -240,7 +329,7 @@ export class GitRunner {
|
|
|
240
329
|
if (attributesFile)
|
|
241
330
|
full.push('-c', `core.attributesFile=${attributesFile}`);
|
|
242
331
|
full.push(...argv);
|
|
243
|
-
const result =
|
|
332
|
+
const result = await spawnGit(cwd, full, opts.timeoutMs ?? GIT_TIMEOUT_MS, runEnv);
|
|
244
333
|
if (result.error) {
|
|
245
334
|
const err = result.error;
|
|
246
335
|
if (err.code === 'ETIMEDOUT')
|
|
@@ -249,7 +338,7 @@ export class GitRunner {
|
|
|
249
338
|
throw new GitWorkspaceError('Git is not installed or not available on PATH', 'missing_git');
|
|
250
339
|
throw new GitWorkspaceError(err.message, classifyGitError(err.message, args));
|
|
251
340
|
}
|
|
252
|
-
const out = { status: result.status ?? 1, stdout: result.stdout
|
|
341
|
+
const out = { status: result.status ?? 1, stdout: result.stdout, stderr: result.stderr };
|
|
253
342
|
if (opts.check && out.status !== 0) {
|
|
254
343
|
const message = (out.stderr || out.stdout || 'Git command failed').trim();
|
|
255
344
|
throw new GitWorkspaceError(message, classifyGitError(message, args));
|
|
@@ -264,7 +353,7 @@ export class GitRunner {
|
|
|
264
353
|
}
|
|
265
354
|
}
|
|
266
355
|
// ── context and paths ────────────────────────────────────────────────────
|
|
267
|
-
resolveContext(workspace) {
|
|
356
|
+
async resolveContext(workspace) {
|
|
268
357
|
const ws = resolvePathLikePython(workspace);
|
|
269
358
|
// Python ran `git rev-parse` with `cwd=<workspace>`; a vanished directory raised `FileNotFoundError`, which `_run_git`
|
|
270
359
|
// reported as `missing_git` — every route answered 400 rather than "not a repo".
|
|
@@ -272,7 +361,7 @@ export class GitRunner {
|
|
|
272
361
|
throw new GitWorkspaceError('Git is not installed or not available on PATH', 'missing_git');
|
|
273
362
|
let result;
|
|
274
363
|
try {
|
|
275
|
-
result = this.run(ws, ['rev-parse', '--show-toplevel']);
|
|
364
|
+
result = await this.run(ws, ['rev-parse', '--show-toplevel']);
|
|
276
365
|
}
|
|
277
366
|
catch (error) {
|
|
278
367
|
if (error instanceof GitWorkspaceError && (error.code === 'missing_git' || error.code === 'timeout'))
|
|
@@ -317,28 +406,46 @@ export class GitRunner {
|
|
|
317
406
|
return null;
|
|
318
407
|
}
|
|
319
408
|
// ── mutation lock ────────────────────────────────────────────────────────
|
|
320
|
-
|
|
321
|
-
|
|
409
|
+
/**
|
|
410
|
+
* The per-repo mutation lock. `holdRepo` (working-tree mutations) also holds the whole repository busy and, in the same
|
|
411
|
+
* tick, refuses when a run or an asynchronous writer is already active anywhere in it.
|
|
412
|
+
*/
|
|
413
|
+
async withMutationLock(ctx, fn, { holdRepo = true } = {}) {
|
|
414
|
+
try {
|
|
415
|
+
return await this.withLock(ctx.repoRoot, () => (holdRepo ? this.holdWorkspace(ctx.repoRoot, () => { this.assertRepoFree(ctx.repoRoot); return fn(); }) : fn()));
|
|
416
|
+
}
|
|
417
|
+
finally {
|
|
418
|
+
this.invalidateStatusCache(ctx.repoRoot);
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
assertRepoFree(repoRoot) {
|
|
422
|
+
if (this.deps.activeRunIn?.(repoRoot))
|
|
423
|
+
throw new GitWorkspaceError('A session run is active. Wait for it to finish before running this Git operation.', 'active_stream');
|
|
424
|
+
if (overlapsAny(this.writing, repoRoot))
|
|
425
|
+
throw new GitWorkspaceError(WORKSPACE_WRITE_BUSY_MESSAGE, 'operation_in_progress');
|
|
426
|
+
}
|
|
427
|
+
async withLock(path, fn) {
|
|
428
|
+
const key = resolvePathLikePython(path);
|
|
322
429
|
const previous = this.locks.get(key) ?? Promise.resolve();
|
|
323
430
|
let release;
|
|
324
431
|
const current = new Promise((r) => { release = r; });
|
|
325
432
|
const chained = previous.then(() => current);
|
|
326
433
|
this.locks.set(key, chained);
|
|
434
|
+
// Drop the entry once this link settles and nothing queued behind it, whether it ran or timed out waiting.
|
|
435
|
+
void chained.then(() => { if (this.locks.get(key) === chained)
|
|
436
|
+
this.locks.delete(key); });
|
|
327
437
|
let timer;
|
|
328
|
-
const acquired = await Promise.race([previous.then(() => true), new Promise((r) => { timer = setTimeout(() => { r(false); }, GIT_REMOTE_TIMEOUT_MS); })]);
|
|
438
|
+
const acquired = await Promise.race([previous.then(() => true), new Promise((r) => { timer = setTimeout(() => { r(false); }, this.deps.mutationLockTimeoutMs ?? GIT_REMOTE_TIMEOUT_MS); })]);
|
|
329
439
|
clearTimeout(timer);
|
|
330
440
|
if (!acquired) {
|
|
331
441
|
release();
|
|
332
442
|
throw new GitWorkspaceError('Another Git operation is still running', 'operation_in_progress');
|
|
333
443
|
}
|
|
334
444
|
try {
|
|
335
|
-
return fn();
|
|
445
|
+
return await fn();
|
|
336
446
|
}
|
|
337
447
|
finally {
|
|
338
|
-
this.invalidateStatusCache(ctx.repoRoot);
|
|
339
448
|
release();
|
|
340
|
-
if (this.locks.get(key) === chained)
|
|
341
|
-
this.locks.delete(key);
|
|
342
449
|
}
|
|
343
450
|
}
|
|
344
451
|
// ── status cache ─────────────────────────────────────────────────────────
|
|
@@ -441,12 +548,12 @@ export class GitRunner {
|
|
|
441
548
|
}
|
|
442
549
|
return stats;
|
|
443
550
|
}
|
|
444
|
-
collectNumstat(ctx, cached) {
|
|
551
|
+
async collectNumstat(ctx, cached) {
|
|
445
552
|
const args = ['diff', '--numstat', '-z', '--no-textconv', '--ignore-cr-at-eol'];
|
|
446
553
|
if (cached)
|
|
447
554
|
args.push('--cached');
|
|
448
555
|
args.push('--', GitRunner.workspacePathspec(ctx));
|
|
449
|
-
const result = this.run(ctx, args, { disableFilterAttributes: this.destructiveEnabled(), neutralizeFilterPrograms: true });
|
|
556
|
+
const result = await this.run(ctx, args, { disableFilterAttributes: this.destructiveEnabled(), neutralizeFilterPrograms: true });
|
|
450
557
|
if (result.status !== 0)
|
|
451
558
|
return null;
|
|
452
559
|
return this.parseNumstat(result.stdout, ctx);
|
|
@@ -502,20 +609,20 @@ export class GitRunner {
|
|
|
502
609
|
return [0, 0, true];
|
|
503
610
|
return [splitLines(text).length || (text ? 1 : 0), 0, false];
|
|
504
611
|
}
|
|
505
|
-
status(workspace, opts = {}) {
|
|
612
|
+
async status(workspace, opts = {}) {
|
|
506
613
|
const resolved = resolvePathLikePython(workspace);
|
|
507
614
|
if (opts.useCache) {
|
|
508
615
|
const cached = this.cachedStatus(resolved);
|
|
509
616
|
if (cached)
|
|
510
617
|
return cached;
|
|
511
618
|
}
|
|
512
|
-
const ctx = this.resolveContext(resolved);
|
|
619
|
+
const ctx = await this.resolveContext(resolved);
|
|
513
620
|
if (!ctx)
|
|
514
621
|
return { is_git: false };
|
|
515
622
|
const generation = this.generations.get(ctx.repoRoot) ?? 0;
|
|
516
|
-
const result = this.run(ctx, ['status', '--porcelain=v2', '-z', '--branch', '--untracked-files=all', '--', GitRunner.workspacePathspec(ctx)], { check: true, disableFilterAttributes: this.destructiveEnabled(), neutralizeFilterPrograms: true });
|
|
517
|
-
const stagedStats = this.collectNumstat(ctx, true);
|
|
518
|
-
const unstagedStats = this.collectNumstat(ctx, false);
|
|
623
|
+
const result = await this.run(ctx, ['status', '--porcelain=v2', '-z', '--branch', '--untracked-files=all', '--', GitRunner.workspacePathspec(ctx)], { check: true, disableFilterAttributes: this.destructiveEnabled(), neutralizeFilterPrograms: true });
|
|
624
|
+
const stagedStats = await this.collectNumstat(ctx, true);
|
|
625
|
+
const unstagedStats = await this.collectNumstat(ctx, false);
|
|
519
626
|
const statSources = [stagedStats ?? new Map(), unstagedStats ?? new Map()];
|
|
520
627
|
let branch = '';
|
|
521
628
|
let upstream = '';
|
|
@@ -660,7 +767,7 @@ export class GitRunner {
|
|
|
660
767
|
totals.conflicts += 1;
|
|
661
768
|
}
|
|
662
769
|
if (!branch)
|
|
663
|
-
branch = this.run(ctx, ['rev-parse', '--short', 'HEAD']).stdout.trim();
|
|
770
|
+
branch = (await this.run(ctx, ['rev-parse', '--short', 'HEAD'])).stdout.trim();
|
|
664
771
|
const payload = {
|
|
665
772
|
is_git: true, branch: branch || 'HEAD', upstream, ahead, behind, totals, files: fileList, truncated,
|
|
666
773
|
noise_filtering: { ...noise, active: noise.filemode_only > 0 || noise.crlf_only > 0 },
|
|
@@ -669,10 +776,10 @@ export class GitRunner {
|
|
|
669
776
|
return payload;
|
|
670
777
|
}
|
|
671
778
|
// ── branches ─────────────────────────────────────────────────────────────
|
|
672
|
-
branchAheadBehind(ctx, branch, upstream) {
|
|
779
|
+
async branchAheadBehind(ctx, branch, upstream) {
|
|
673
780
|
if (!upstream)
|
|
674
781
|
return [0, 0];
|
|
675
|
-
const result = this.run(ctx, ['rev-list', '--left-right', '--count', `${branch}...${upstream}`]);
|
|
782
|
+
const result = await this.run(ctx, ['rev-list', '--left-right', '--count', `${branch}...${upstream}`]);
|
|
676
783
|
if (result.status !== 0)
|
|
677
784
|
return [0, 0];
|
|
678
785
|
const parts = result.stdout.trim().split(/\s+/);
|
|
@@ -682,9 +789,9 @@ export class GitRunner {
|
|
|
682
789
|
const b = Number.parseInt(parts[1] ?? '', 10);
|
|
683
790
|
return Number.isFinite(a) && Number.isFinite(b) ? [a, b] : [0, 0];
|
|
684
791
|
}
|
|
685
|
-
forEachRef(ctx, refPrefix) {
|
|
792
|
+
async forEachRef(ctx, refPrefix) {
|
|
686
793
|
const fmt = '%(refname)%00%(refname:short)%00%(upstream:short)%00%(objectname:short)%00%(committerdate:unix)%00%(committerdate:relative)%00%(authorname)%00%(subject)';
|
|
687
|
-
const result = this.run(ctx, ['for-each-ref', `--format=${fmt}`, refPrefix], { check: true });
|
|
794
|
+
const result = await this.run(ctx, ['for-each-ref', `--format=${fmt}`, refPrefix], { check: true });
|
|
688
795
|
const refs = [];
|
|
689
796
|
for (const line of result.stdout.split('\n')) {
|
|
690
797
|
const [fullName = '', name = '', upstream = '', sha = '', updated = '', updatedRelative = '', author = '', subject = ''] = line.split('\0');
|
|
@@ -694,65 +801,65 @@ export class GitRunner {
|
|
|
694
801
|
continue;
|
|
695
802
|
const item = { name, sha, updated: /^\d+$/.test(updated) ? Number.parseInt(updated, 10) : 0, updated_relative: updatedRelative, author, subject, upstream: '', ahead: 0, behind: 0 };
|
|
696
803
|
if (upstream) {
|
|
697
|
-
const [ahead, behind] = this.branchAheadBehind(ctx, name, upstream);
|
|
804
|
+
const [ahead, behind] = await this.branchAheadBehind(ctx, name, upstream);
|
|
698
805
|
Object.assign(item, { upstream, ahead, behind });
|
|
699
806
|
}
|
|
700
807
|
refs.push(item);
|
|
701
808
|
}
|
|
702
809
|
return refs.sort((a, b) => (a.name.toLowerCase() < b.name.toLowerCase() ? -1 : a.name.toLowerCase() > b.name.toLowerCase() ? 1 : 0));
|
|
703
810
|
}
|
|
704
|
-
branches(workspace) {
|
|
705
|
-
const ctx = this.resolveContext(workspace);
|
|
811
|
+
async branches(workspace) {
|
|
812
|
+
const ctx = await this.resolveContext(workspace);
|
|
706
813
|
if (!ctx)
|
|
707
814
|
throw new GitWorkspaceError('Workspace is not a Git repository', 'not_a_repo');
|
|
708
|
-
const headName = this.run(ctx, ['branch', '--show-current'], { check: true }).stdout.trim();
|
|
709
|
-
const headSha = this.run(ctx, ['rev-parse', '--short', 'HEAD'], { check: true }).stdout.trim();
|
|
710
|
-
const status = this.status(workspace);
|
|
815
|
+
const headName = (await this.run(ctx, ['branch', '--show-current'], { check: true })).stdout.trim();
|
|
816
|
+
const headSha = (await this.run(ctx, ['rev-parse', '--short', 'HEAD'], { check: true })).stdout.trim();
|
|
817
|
+
const status = await this.status(workspace);
|
|
711
818
|
return {
|
|
712
819
|
is_git: true, current: headName || headSha || 'HEAD', detached: !headName, head: headSha,
|
|
713
|
-
local: this.forEachRef(ctx, 'refs/heads'), remote: this.forEachRef(ctx, 'refs/remotes'),
|
|
820
|
+
local: await this.forEachRef(ctx, 'refs/heads'), remote: await this.forEachRef(ctx, 'refs/remotes'),
|
|
714
821
|
upstream: status.upstream ?? '', ahead: status.ahead ?? 0, behind: status.behind ?? 0,
|
|
715
822
|
};
|
|
716
823
|
}
|
|
717
824
|
// ── checkout ─────────────────────────────────────────────────────────────
|
|
718
|
-
validateLocalBranch(ctx, ref) {
|
|
825
|
+
async validateLocalBranch(ctx, ref) {
|
|
719
826
|
const r = ref.trim();
|
|
720
827
|
if (!r)
|
|
721
828
|
throw new GitWorkspaceError('Branch name is required', 'invalid_ref');
|
|
722
|
-
this.run(ctx, ['show-ref', '--verify', `refs/heads/${r}`], { check: true });
|
|
829
|
+
await this.run(ctx, ['show-ref', '--verify', `refs/heads/${r}`], { check: true });
|
|
723
830
|
return r;
|
|
724
831
|
}
|
|
725
|
-
validateRemoteBranch(ctx, ref) {
|
|
832
|
+
async validateRemoteBranch(ctx, ref) {
|
|
726
833
|
const r = ref.trim();
|
|
727
834
|
if (!r)
|
|
728
835
|
throw new GitWorkspaceError('Remote branch name is required', 'invalid_ref');
|
|
729
|
-
this.run(ctx, ['show-ref', '--verify', `refs/remotes/${r}`], { check: true });
|
|
836
|
+
await this.run(ctx, ['show-ref', '--verify', `refs/remotes/${r}`], { check: true });
|
|
730
837
|
return r;
|
|
731
838
|
}
|
|
732
|
-
validateCheckoutStart(ctx, ref) {
|
|
839
|
+
async validateCheckoutStart(ctx, ref) {
|
|
733
840
|
const r = (ref || 'HEAD').trim() || 'HEAD';
|
|
734
|
-
if (this.run(ctx, ['rev-parse', '--verify', `${r}^{commit}`]).status !== 0)
|
|
841
|
+
if ((await this.run(ctx, ['rev-parse', '--verify', `${r}^{commit}`])).status !== 0)
|
|
735
842
|
throw new GitWorkspaceError('Invalid checkout reference', 'invalid_ref');
|
|
736
843
|
return r;
|
|
737
844
|
}
|
|
738
|
-
validateNewBranchName(ctx, name) {
|
|
845
|
+
async validateNewBranchName(ctx, name) {
|
|
739
846
|
const n = name.trim();
|
|
740
847
|
if (!n)
|
|
741
848
|
throw new GitWorkspaceError('New branch name is required', 'invalid_ref');
|
|
742
|
-
if (this.run(ctx, ['check-ref-format', '--branch', n]).status !== 0)
|
|
849
|
+
if ((await this.run(ctx, ['check-ref-format', '--branch', n])).status !== 0)
|
|
743
850
|
throw new GitWorkspaceError('Invalid branch name', 'invalid_ref');
|
|
744
|
-
if (this.run(ctx, ['show-ref', '--verify', `refs/heads/${n}`]).status === 0)
|
|
851
|
+
if ((await this.run(ctx, ['show-ref', '--verify', `refs/heads/${n}`])).status === 0)
|
|
745
852
|
throw new GitWorkspaceError('A local branch with that name already exists', 'invalid_ref');
|
|
746
853
|
return n;
|
|
747
854
|
}
|
|
748
|
-
dirtyWorktree(ctx) {
|
|
749
|
-
return this.run(ctx, ['status', '--porcelain=v2', '--untracked-files=all'], { check: true, neutralizeFilterPrograms: true }).stdout.trim() !== '';
|
|
855
|
+
async dirtyWorktree(ctx) {
|
|
856
|
+
return (await this.run(ctx, ['status', '--porcelain=v2', '--untracked-files=all'], { check: true, neutralizeFilterPrograms: true })).stdout.trim() !== '';
|
|
750
857
|
}
|
|
751
|
-
currentCheckoutLabel(ctx) {
|
|
752
|
-
const branch = this.run(ctx, ['branch', '--show-current']).stdout.trim();
|
|
858
|
+
async currentCheckoutLabel(ctx) {
|
|
859
|
+
const branch = (await this.run(ctx, ['branch', '--show-current'])).stdout.trim();
|
|
753
860
|
if (branch)
|
|
754
861
|
return branch;
|
|
755
|
-
return this.run(ctx, ['rev-parse', '--short', 'HEAD'], { check: true }).stdout.trim() || 'HEAD';
|
|
862
|
+
return (await this.run(ctx, ['rev-parse', '--short', 'HEAD'], { check: true })).stdout.trim() || 'HEAD';
|
|
756
863
|
}
|
|
757
864
|
static stashSubjectParts(subject) {
|
|
758
865
|
const s = subject.trim();
|
|
@@ -765,8 +872,8 @@ export class GitRunner {
|
|
|
765
872
|
return null;
|
|
766
873
|
return [branch, message];
|
|
767
874
|
}
|
|
768
|
-
branchSwitchStashes(ctx) {
|
|
769
|
-
const result = this.run(ctx, ['stash', 'list', '--format=%gd%x00%gs']);
|
|
875
|
+
async branchSwitchStashes(ctx) {
|
|
876
|
+
const result = await this.run(ctx, ['stash', 'list', '--format=%gd%x00%gs']);
|
|
770
877
|
if (result.status !== 0)
|
|
771
878
|
return [];
|
|
772
879
|
const out = [];
|
|
@@ -781,129 +888,127 @@ export class GitRunner {
|
|
|
781
888
|
}
|
|
782
889
|
return out;
|
|
783
890
|
}
|
|
784
|
-
restoreBranchSwitchStashLocked(ctx, branch) {
|
|
785
|
-
if (this.destructiveEnabled() && this.hasRepoLocalFilters(ctx.repoRoot))
|
|
891
|
+
async restoreBranchSwitchStashLocked(ctx, branch) {
|
|
892
|
+
if (this.destructiveEnabled() && await this.hasRepoLocalFilters(ctx.repoRoot))
|
|
786
893
|
return { restore_blocked: true, restore_reason: 'Repository defines local filter programs' };
|
|
787
|
-
if (this.dirtyWorktree(ctx))
|
|
894
|
+
if (await this.dirtyWorktree(ctx))
|
|
788
895
|
return {};
|
|
789
|
-
for (const item of this.branchSwitchStashes(ctx)) {
|
|
896
|
+
for (const item of await this.branchSwitchStashes(ctx)) {
|
|
790
897
|
if (item.branch !== branch)
|
|
791
898
|
continue;
|
|
792
|
-
const result = this.run(ctx, ['stash', 'pop', '--index', item.ref], { destructive: true, disableFilterAttributes: true });
|
|
899
|
+
const result = await this.run(ctx, ['stash', 'pop', '--index', item.ref], { destructive: true, disableFilterAttributes: true });
|
|
793
900
|
if (result.status === 0)
|
|
794
901
|
return { restored_stash: item };
|
|
795
902
|
return { restore_failed: true, restore_error: (result.stderr || result.stdout || 'Git stash restore failed').trim(), restore_stash: item };
|
|
796
903
|
}
|
|
797
904
|
return {};
|
|
798
905
|
}
|
|
799
|
-
validateCheckoutRequestLocked(ctx, ref, mode, newBranch) {
|
|
906
|
+
async validateCheckoutRequestLocked(ctx, ref, mode, newBranch) {
|
|
800
907
|
if (mode === 'local') {
|
|
801
|
-
this.validateLocalBranch(ctx, ref);
|
|
908
|
+
await this.validateLocalBranch(ctx, ref);
|
|
802
909
|
return;
|
|
803
910
|
}
|
|
804
911
|
if (mode === 'new' || mode === 'create') {
|
|
805
|
-
this.validateNewBranchName(ctx, newBranch || ref);
|
|
806
|
-
this.validateCheckoutStart(ctx, newBranch && ref && ref !== newBranch ? ref : 'HEAD');
|
|
912
|
+
await this.validateNewBranchName(ctx, newBranch || ref);
|
|
913
|
+
await this.validateCheckoutStart(ctx, newBranch && ref && ref !== newBranch ? ref : 'HEAD');
|
|
807
914
|
return;
|
|
808
915
|
}
|
|
809
916
|
if (mode === 'remote') {
|
|
810
|
-
const remoteRef = this.validateRemoteBranch(ctx, ref);
|
|
917
|
+
const remoteRef = await this.validateRemoteBranch(ctx, ref);
|
|
811
918
|
const branchName = (newBranch || remoteRef.split('/').slice(1).join('/')).trim();
|
|
812
|
-
if (this.run(ctx, ['show-ref', '--verify', `refs/heads/${branchName}`]).status !== 0)
|
|
813
|
-
this.validateNewBranchName(ctx, branchName);
|
|
919
|
+
if ((await this.run(ctx, ['show-ref', '--verify', `refs/heads/${branchName}`])).status !== 0)
|
|
920
|
+
await this.validateNewBranchName(ctx, branchName);
|
|
814
921
|
return;
|
|
815
922
|
}
|
|
816
923
|
if (mode === 'detached' || mode === 'detach') {
|
|
817
|
-
this.validateCheckoutStart(ctx, ref);
|
|
924
|
+
await this.validateCheckoutStart(ctx, ref);
|
|
818
925
|
return;
|
|
819
926
|
}
|
|
820
927
|
throw new GitWorkspaceError('Unsupported checkout mode', 'invalid_ref');
|
|
821
928
|
}
|
|
822
|
-
performCheckoutLocked(ctx, ref, mode, newBranch, track) {
|
|
823
|
-
if (this.destructiveEnabled() && this.hasRepoLocalFilters(ctx.repoRoot)) {
|
|
929
|
+
async performCheckoutLocked(ctx, ref, mode, newBranch, track) {
|
|
930
|
+
if (this.destructiveEnabled() && await this.hasRepoLocalFilters(ctx.repoRoot)) {
|
|
824
931
|
throw new GitWorkspaceError('Cannot checkout: repository defines local filter programs that would alter file content', 'filtered_path');
|
|
825
932
|
}
|
|
826
933
|
const destructive = { check: true, destructive: true, disableFilterAttributes: true };
|
|
827
934
|
if (mode === 'local')
|
|
828
|
-
return this.run(ctx, ['switch', '--recurse-submodules=no', this.validateLocalBranch(ctx, ref)], destructive);
|
|
935
|
+
return await this.run(ctx, ['switch', '--recurse-submodules=no', await this.validateLocalBranch(ctx, ref)], destructive);
|
|
829
936
|
if (mode === 'new' || mode === 'create') {
|
|
830
|
-
const branch = this.validateNewBranchName(ctx, newBranch || ref);
|
|
831
|
-
const startRef = this.validateCheckoutStart(ctx, newBranch && ref && ref !== newBranch ? ref : 'HEAD');
|
|
832
|
-
return this.run(ctx, ['switch', '--recurse-submodules=no', '-c', branch, startRef], destructive);
|
|
937
|
+
const branch = await this.validateNewBranchName(ctx, newBranch || ref);
|
|
938
|
+
const startRef = await this.validateCheckoutStart(ctx, newBranch && ref && ref !== newBranch ? ref : 'HEAD');
|
|
939
|
+
return await this.run(ctx, ['switch', '--recurse-submodules=no', '-c', branch, startRef], destructive);
|
|
833
940
|
}
|
|
834
941
|
if (mode === 'remote') {
|
|
835
|
-
const remoteRef = this.validateRemoteBranch(ctx, ref);
|
|
942
|
+
const remoteRef = await this.validateRemoteBranch(ctx, ref);
|
|
836
943
|
const branchName = (newBranch || remoteRef.split('/').slice(1).join('/')).trim();
|
|
837
|
-
if (this.run(ctx, ['show-ref', '--verify', `refs/heads/${branchName}`]).status === 0) {
|
|
838
|
-
const result = this.run(ctx, ['switch', '--recurse-submodules=no', branchName], destructive);
|
|
944
|
+
if ((await this.run(ctx, ['show-ref', '--verify', `refs/heads/${branchName}`])).status === 0) {
|
|
945
|
+
const result = await this.run(ctx, ['switch', '--recurse-submodules=no', branchName], destructive);
|
|
839
946
|
if (track)
|
|
840
|
-
this.run(ctx, ['branch', '--set-upstream-to', remoteRef, branchName]);
|
|
947
|
+
await this.run(ctx, ['branch', '--set-upstream-to', remoteRef, branchName]);
|
|
841
948
|
return result;
|
|
842
949
|
}
|
|
843
|
-
const branch = this.validateNewBranchName(ctx, branchName);
|
|
950
|
+
const branch = await this.validateNewBranchName(ctx, branchName);
|
|
844
951
|
const args = ['switch', '--recurse-submodules=no', '-c', branch];
|
|
845
952
|
if (track)
|
|
846
953
|
args.push('--track');
|
|
847
954
|
args.push(remoteRef);
|
|
848
|
-
return this.run(ctx, args, destructive);
|
|
955
|
+
return await this.run(ctx, args, destructive);
|
|
849
956
|
}
|
|
850
957
|
if (mode === 'detached' || mode === 'detach')
|
|
851
|
-
return this.run(ctx, ['switch', '--recurse-submodules=no', '--detach', this.validateCheckoutStart(ctx, ref)], destructive);
|
|
958
|
+
return await this.run(ctx, ['switch', '--recurse-submodules=no', '--detach', await this.validateCheckoutStart(ctx, ref)], destructive);
|
|
852
959
|
throw new GitWorkspaceError('Unsupported checkout mode', 'invalid_ref');
|
|
853
960
|
}
|
|
854
961
|
static remoteMessage(result) {
|
|
855
962
|
return (result.stdout || result.stderr || '').trim();
|
|
856
963
|
}
|
|
857
964
|
async checkout(workspace, ref, mode, opts = {}) {
|
|
858
|
-
const ctx = this.resolveContext(workspace);
|
|
965
|
+
const ctx = await this.resolveContext(workspace);
|
|
859
966
|
if (!ctx)
|
|
860
967
|
throw new GitWorkspaceError('Workspace is not a Git repository', 'not_a_repo');
|
|
861
968
|
const m = (mode || 'local').trim().toLowerCase();
|
|
862
969
|
const dirtyMode = (opts.dirtyMode || 'block').trim().toLowerCase();
|
|
863
970
|
if (dirtyMode !== 'block')
|
|
864
971
|
throw new GitWorkspaceError('Only dirty_mode=block is supported for branch checkout', 'dirty_worktree');
|
|
865
|
-
const result = await this.withMutationLock(ctx, () => {
|
|
866
|
-
this.validateCheckoutRequestLocked(ctx, ref, m, opts.newBranch ?? null);
|
|
867
|
-
if (this.dirtyWorktree(ctx))
|
|
972
|
+
const result = await this.withMutationLock(ctx, async () => {
|
|
973
|
+
await this.validateCheckoutRequestLocked(ctx, ref, m, opts.newBranch ?? null);
|
|
974
|
+
if (await this.dirtyWorktree(ctx))
|
|
868
975
|
throw new GitWorkspaceError('Checkout blocked because the Git worktree has uncommitted changes', 'dirty_worktree');
|
|
869
|
-
|
|
976
|
+
const checkedOut = await this.performCheckoutLocked(ctx, ref, m, opts.newBranch ?? null, Boolean(opts.track));
|
|
977
|
+
return { checkedOut, status: await this.status(workspace), branches: await this.branches(workspace) };
|
|
870
978
|
});
|
|
871
|
-
const status =
|
|
872
|
-
|
|
873
|
-
return { ok: true, message: GitRunner.remoteMessage(result), current_branch: branches.current, status, branches };
|
|
979
|
+
const { status, branches } = result;
|
|
980
|
+
return { ok: true, message: GitRunner.remoteMessage(result.checkedOut), current_branch: branches.current, status, branches };
|
|
874
981
|
}
|
|
875
982
|
async stashAndCheckout(workspace, ref, mode, opts = {}) {
|
|
876
|
-
const ctx = this.resolveContext(workspace);
|
|
983
|
+
const ctx = await this.resolveContext(workspace);
|
|
877
984
|
if (!ctx)
|
|
878
985
|
throw new GitWorkspaceError('Workspace is not a Git repository', 'not_a_repo');
|
|
879
986
|
const m = (mode || 'local').trim().toLowerCase();
|
|
880
987
|
const targetLabel = (opts.newBranch || ref || 'HEAD').trim() || 'HEAD';
|
|
881
988
|
const stashName = `${BRANCH_SWITCH_STASH_PREFIX} to ${targetLabel}`.trim();
|
|
882
|
-
const outcome = await this.withMutationLock(ctx, () => {
|
|
883
|
-
this.validateCheckoutRequestLocked(ctx, ref, m, opts.newBranch ?? null);
|
|
884
|
-
if (this.destructiveEnabled() && this.hasRepoLocalFilters(ctx.repoRoot)) {
|
|
989
|
+
const outcome = await this.withMutationLock(ctx, async () => {
|
|
990
|
+
await this.validateCheckoutRequestLocked(ctx, ref, m, opts.newBranch ?? null);
|
|
991
|
+
if (this.destructiveEnabled() && await this.hasRepoLocalFilters(ctx.repoRoot)) {
|
|
885
992
|
throw new GitWorkspaceError('Cannot stash: repository defines local filter programs that would alter file content', 'filtered_path');
|
|
886
993
|
}
|
|
887
994
|
let stashed = false;
|
|
888
|
-
if (this.dirtyWorktree(ctx)) {
|
|
889
|
-
const stashResult = this.run(ctx, ['stash', 'push', '-u', '-m', stashName], { check: true, destructive: true, disableFilterAttributes: true });
|
|
995
|
+
if (await this.dirtyWorktree(ctx)) {
|
|
996
|
+
const stashResult = await this.run(ctx, ['stash', 'push', '-u', '-m', stashName], { check: true, destructive: true, disableFilterAttributes: true });
|
|
890
997
|
stashed = !GitRunner.remoteMessage(stashResult).includes('No local changes to save');
|
|
891
998
|
}
|
|
892
999
|
let result;
|
|
893
1000
|
try {
|
|
894
|
-
result = this.performCheckoutLocked(ctx, ref, m, opts.newBranch ?? null, Boolean(opts.track));
|
|
1001
|
+
result = await this.performCheckoutLocked(ctx, ref, m, opts.newBranch ?? null, Boolean(opts.track));
|
|
895
1002
|
}
|
|
896
1003
|
catch (error) {
|
|
897
1004
|
if (stashed)
|
|
898
|
-
this.run(ctx, ['stash', 'pop', '--index', 'stash@{0}'], { destructive: true, disableFilterAttributes: true });
|
|
1005
|
+
await this.run(ctx, ['stash', 'pop', '--index', 'stash@{0}'], { destructive: true, disableFilterAttributes: true });
|
|
899
1006
|
throw error;
|
|
900
1007
|
}
|
|
901
|
-
const restored = this.restoreBranchSwitchStashLocked(ctx, this.currentCheckoutLabel(ctx));
|
|
902
|
-
return { result, stashed, restored };
|
|
1008
|
+
const restored = await this.restoreBranchSwitchStashLocked(ctx, await this.currentCheckoutLabel(ctx));
|
|
1009
|
+
return { result, stashed, restored, status: await this.status(workspace), branches: await this.branches(workspace) };
|
|
903
1010
|
});
|
|
904
|
-
const status =
|
|
905
|
-
const branches = this.branches(workspace);
|
|
906
|
-
const { restored } = outcome;
|
|
1011
|
+
const { restored, status, branches } = outcome;
|
|
907
1012
|
return {
|
|
908
1013
|
ok: true, message: GitRunner.remoteMessage(outcome.result), stash_name: outcome.stashed ? stashName : '', stashed: outcome.stashed,
|
|
909
1014
|
restored_stash: restored.restored_stash ?? null, restore_failed: Boolean(restored.restore_failed), restore_error: str(restored.restore_error),
|
|
@@ -929,15 +1034,15 @@ export class GitRunner {
|
|
|
929
1034
|
const [additions, deletions] = diffStats(diff);
|
|
930
1035
|
return { binary: false, too_large: tooLarge, diff, additions, deletions };
|
|
931
1036
|
}
|
|
932
|
-
diff(workspace, path, kind = 'unstaged') {
|
|
933
|
-
const ctx = this.resolveContext(workspace);
|
|
1037
|
+
async diff(workspace, path, kind = 'unstaged') {
|
|
1038
|
+
const ctx = await this.resolveContext(workspace);
|
|
934
1039
|
if (!ctx)
|
|
935
1040
|
throw new GitWorkspaceError('Workspace is not a Git repository');
|
|
936
1041
|
if (kind !== 'unstaged' && kind !== 'staged')
|
|
937
1042
|
throw new GitWorkspaceError('kind must be staged or unstaged');
|
|
938
1043
|
const repoRel = GitRunner.repoRel(ctx, path);
|
|
939
1044
|
const workspaceRel = GitRunner.workspaceRel(ctx, repoRel) ?? path;
|
|
940
|
-
const status = this.status(workspace);
|
|
1045
|
+
const status = await this.status(workspace);
|
|
941
1046
|
const fileState = (status.files ?? []).find((f) => f.path === workspaceRel);
|
|
942
1047
|
if (kind === 'unstaged' && fileState?.untracked)
|
|
943
1048
|
return { path: workspaceRel, kind, ...GitRunner.syntheticUntrackedDiff(ctx.workspace, join(ctx.workspace, workspaceRel), workspaceRel) };
|
|
@@ -945,7 +1050,7 @@ export class GitRunner {
|
|
|
945
1050
|
if (kind === 'staged')
|
|
946
1051
|
args.push('--cached');
|
|
947
1052
|
args.push('--', repoRel);
|
|
948
|
-
let diff = this.run(ctx, args, { check: true, neutralizeFilterPrograms: true }).stdout;
|
|
1053
|
+
let diff = (await this.run(ctx, args, { check: true, neutralizeFilterPrograms: true })).stdout;
|
|
949
1054
|
const binary = diff.includes('Binary files ') || diff.includes('GIT binary patch');
|
|
950
1055
|
const tooLarge = Buffer.byteLength(diff, 'utf8') > DIFF_SIZE_LIMIT;
|
|
951
1056
|
if (tooLarge)
|
|
@@ -968,39 +1073,39 @@ export class GitRunner {
|
|
|
968
1073
|
static pathspecs(ctx, paths) {
|
|
969
1074
|
return GitRunner.cleanPaths(paths).map((p) => GitRunner.repoRel(ctx, p));
|
|
970
1075
|
}
|
|
971
|
-
blockFilteredDestructiveWrite(ctx, message) {
|
|
972
|
-
if (this.destructiveEnabled() && this.hasRepoLocalFilters(ctx.repoRoot))
|
|
1076
|
+
async blockFilteredDestructiveWrite(ctx, message) {
|
|
1077
|
+
if (this.destructiveEnabled() && await this.hasRepoLocalFilters(ctx.repoRoot))
|
|
973
1078
|
throw new GitWorkspaceError(message, 'filtered_path');
|
|
974
1079
|
}
|
|
975
|
-
requireContext(workspace) {
|
|
976
|
-
const ctx = this.resolveContext(workspace);
|
|
1080
|
+
async requireContext(workspace) {
|
|
1081
|
+
const ctx = await this.resolveContext(workspace);
|
|
977
1082
|
if (!ctx)
|
|
978
1083
|
throw new GitWorkspaceError('Workspace is not a Git repository', 'not_a_repo');
|
|
979
1084
|
return ctx;
|
|
980
1085
|
}
|
|
981
1086
|
async stage(workspace, paths) {
|
|
982
|
-
const ctx = this.requireContext(workspace);
|
|
983
|
-
await this.withMutationLock(ctx, () => {
|
|
984
|
-
this.blockFilteredDestructiveWrite(ctx, 'Repository uses local Git filters; stage may corrupt index content. Use the terminal to stage manually.');
|
|
985
|
-
this.run(ctx, ['add', '--', ...GitRunner.pathspecs(ctx, paths)], { check: true, destructive: true, disableFilterAttributes: true });
|
|
1087
|
+
const ctx = await this.requireContext(workspace);
|
|
1088
|
+
return await this.withMutationLock(ctx, async () => {
|
|
1089
|
+
await this.blockFilteredDestructiveWrite(ctx, 'Repository uses local Git filters; stage may corrupt index content. Use the terminal to stage manually.');
|
|
1090
|
+
await this.run(ctx, ['add', '--', ...GitRunner.pathspecs(ctx, paths)], { check: true, destructive: true, disableFilterAttributes: true });
|
|
1091
|
+
return await this.status(workspace);
|
|
986
1092
|
});
|
|
987
|
-
return this.status(workspace);
|
|
988
1093
|
}
|
|
989
1094
|
async unstage(workspace, paths) {
|
|
990
|
-
const ctx = this.requireContext(workspace);
|
|
1095
|
+
const ctx = await this.requireContext(workspace);
|
|
991
1096
|
const specs = GitRunner.pathspecs(ctx, paths);
|
|
992
|
-
await this.withMutationLock(ctx, () => {
|
|
993
|
-
const result = this.run(ctx, ['restore', '--staged', '--', ...specs], { destructive: true });
|
|
1097
|
+
return await this.withMutationLock(ctx, async () => {
|
|
1098
|
+
const result = await this.run(ctx, ['restore', '--staged', '--', ...specs], { destructive: true });
|
|
994
1099
|
if (result.status !== 0)
|
|
995
|
-
this.run(ctx, ['reset', 'HEAD', '--', ...specs], { check: true, destructive: true });
|
|
1100
|
+
await this.run(ctx, ['reset', 'HEAD', '--', ...specs], { check: true, destructive: true });
|
|
1101
|
+
return await this.status(workspace);
|
|
996
1102
|
});
|
|
997
|
-
return this.status(workspace);
|
|
998
1103
|
}
|
|
999
1104
|
async discard(workspace, paths, opts = {}) {
|
|
1000
|
-
const ctx = this.requireContext(workspace);
|
|
1001
|
-
await this.withMutationLock(ctx, () => {
|
|
1002
|
-
this.blockFilteredDestructiveWrite(ctx, 'Repository uses local Git filters; discard may corrupt working-tree content. Use the terminal to discard manually.');
|
|
1003
|
-
const status = this.status(workspace);
|
|
1105
|
+
const ctx = await this.requireContext(workspace);
|
|
1106
|
+
return await this.withMutationLock(ctx, async () => {
|
|
1107
|
+
await this.blockFilteredDestructiveWrite(ctx, 'Repository uses local Git filters; discard may corrupt working-tree content. Use the terminal to discard manually.');
|
|
1108
|
+
const status = await this.status(workspace);
|
|
1004
1109
|
const byPath = new Map((status.files ?? []).map((f) => [f.path, f]));
|
|
1005
1110
|
for (const path of GitRunner.cleanPaths(paths)) {
|
|
1006
1111
|
const repoRel = GitRunner.repoRel(ctx, path);
|
|
@@ -1032,14 +1137,14 @@ export class GitRunner {
|
|
|
1032
1137
|
}
|
|
1033
1138
|
continue;
|
|
1034
1139
|
}
|
|
1035
|
-
this.run(ctx, ['restore', '--worktree', '--', repoRel], { check: true, destructive: true, disableFilterAttributes: true });
|
|
1140
|
+
await this.run(ctx, ['restore', '--worktree', '--', repoRel], { check: true, destructive: true, disableFilterAttributes: true });
|
|
1036
1141
|
}
|
|
1142
|
+
return await this.status(workspace);
|
|
1037
1143
|
});
|
|
1038
|
-
return this.status(workspace);
|
|
1039
1144
|
}
|
|
1040
1145
|
// ── commit messages ──────────────────────────────────────────────────────
|
|
1041
|
-
stagedDiffText(ctx) {
|
|
1042
|
-
const diff = this.run(ctx, ['diff', '--cached', '--no-ext-diff', '--no-textconv', '--unified=3', '--', GitRunner.workspacePathspec(ctx)], { check: true, neutralizeFilterPrograms: true }).stdout;
|
|
1146
|
+
async stagedDiffText(ctx) {
|
|
1147
|
+
const diff = (await this.run(ctx, ['diff', '--cached', '--no-ext-diff', '--no-textconv', '--unified=3', '--', GitRunner.workspacePathspec(ctx)], { check: true, neutralizeFilterPrograms: true })).stdout;
|
|
1043
1148
|
return GitRunner.capDiff(diff);
|
|
1044
1149
|
}
|
|
1045
1150
|
static capDiff(diff) {
|
|
@@ -1048,18 +1153,18 @@ export class GitRunner {
|
|
|
1048
1153
|
return [diff, false];
|
|
1049
1154
|
return [encoded.subarray(0, COMMIT_MESSAGE_DIFF_LIMIT).toString('utf8'), true];
|
|
1050
1155
|
}
|
|
1051
|
-
selectedTempIndexEnv(ctx, specs) {
|
|
1052
|
-
this.blockFilteredDestructiveWrite(ctx, 'Repository uses local Git filters; selected commit staging may corrupt index content. Use the terminal to commit manually.');
|
|
1156
|
+
async selectedTempIndexEnv(ctx, specs) {
|
|
1157
|
+
await this.blockFilteredDestructiveWrite(ctx, 'Repository uses local Git filters; selected commit staging may corrupt index content. Use the terminal to commit manually.');
|
|
1053
1158
|
const dir = mkdtempSync(join(tmpdir(), 'hermes-webui-git-index-'));
|
|
1054
1159
|
const indexPath = join(dir, 'index');
|
|
1055
1160
|
const env = { GIT_INDEX_FILE: indexPath };
|
|
1056
1161
|
try {
|
|
1057
|
-
const head = this.run(ctx, ['rev-parse', '--verify', 'HEAD'], { env, destructive: true });
|
|
1162
|
+
const head = await this.run(ctx, ['rev-parse', '--verify', 'HEAD'], { env, destructive: true });
|
|
1058
1163
|
if (head.status === 0)
|
|
1059
|
-
this.run(ctx, ['read-tree', 'HEAD'], { check: true, env, destructive: true });
|
|
1164
|
+
await this.run(ctx, ['read-tree', 'HEAD'], { check: true, env, destructive: true });
|
|
1060
1165
|
else
|
|
1061
|
-
this.run(ctx, ['read-tree', '--empty'], { check: true, env, destructive: true });
|
|
1062
|
-
this.run(ctx, ['add', '-A', '--', ...specs], { check: true, env, destructive: true, disableFilterAttributes: true });
|
|
1166
|
+
await this.run(ctx, ['read-tree', '--empty'], { check: true, env, destructive: true });
|
|
1167
|
+
await this.run(ctx, ['add', '-A', '--', ...specs], { check: true, env, destructive: true, disableFilterAttributes: true });
|
|
1063
1168
|
return [env, dir];
|
|
1064
1169
|
}
|
|
1065
1170
|
catch (error) {
|
|
@@ -1067,11 +1172,11 @@ export class GitRunner {
|
|
|
1067
1172
|
throw error;
|
|
1068
1173
|
}
|
|
1069
1174
|
}
|
|
1070
|
-
selectedFiles(ctx, paths) {
|
|
1175
|
+
async selectedFiles(ctx, paths) {
|
|
1071
1176
|
const requested = GitRunner.cleanPaths(paths);
|
|
1072
1177
|
const requestedSpecs = requested.map((p) => GitRunner.repoRel(ctx, p));
|
|
1073
1178
|
const workspacePaths = requestedSpecs.map((spec, i) => GitRunner.workspaceRel(ctx, spec) ?? requested[i] ?? spec);
|
|
1074
|
-
const status = this.status(ctx.workspace);
|
|
1179
|
+
const status = await this.status(ctx.workspace);
|
|
1075
1180
|
const byPath = new Map((status.files ?? []).map((f) => [f.path, f]));
|
|
1076
1181
|
const specs = [];
|
|
1077
1182
|
const selected = [];
|
|
@@ -1092,10 +1197,10 @@ export class GitRunner {
|
|
|
1092
1197
|
throw new GitWorkspaceError('Selected paths have no committable changes');
|
|
1093
1198
|
return [specs, workspacePaths, selected];
|
|
1094
1199
|
}
|
|
1095
|
-
selectedDiffText(ctx, specs) {
|
|
1096
|
-
const [env, dir] = this.selectedTempIndexEnv(ctx, specs);
|
|
1200
|
+
async selectedDiffText(ctx, specs) {
|
|
1201
|
+
const [env, dir] = await this.selectedTempIndexEnv(ctx, specs);
|
|
1097
1202
|
try {
|
|
1098
|
-
return GitRunner.capDiff(this.run(ctx, ['diff', '--cached', '--no-ext-diff', '--no-textconv', '--unified=3', '--', ...specs], { check: true, env, destructive: true, disableFilterAttributes: true }).stdout);
|
|
1203
|
+
return GitRunner.capDiff((await this.run(ctx, ['diff', '--cached', '--no-ext-diff', '--no-textconv', '--unified=3', '--', ...specs], { check: true, env, destructive: true, disableFilterAttributes: true })).stdout);
|
|
1099
1204
|
}
|
|
1100
1205
|
finally {
|
|
1101
1206
|
rmSync(dir, { recursive: true, force: true });
|
|
@@ -1107,24 +1212,24 @@ export class GitRunner {
|
|
|
1107
1212
|
lines.push(`- ... ${files.length - 80} more ${label} file(s)`);
|
|
1108
1213
|
return lines;
|
|
1109
1214
|
}
|
|
1110
|
-
selectedCommitMessagePrompt(workspace, paths) {
|
|
1111
|
-
const ctx = this.requireContext(workspace);
|
|
1112
|
-
const [specs, , selected] = this.selectedFiles(ctx, paths);
|
|
1113
|
-
const [diff, truncated] = this.selectedDiffText(ctx, specs);
|
|
1215
|
+
async selectedCommitMessagePrompt(workspace, paths) {
|
|
1216
|
+
const ctx = await this.requireContext(workspace);
|
|
1217
|
+
const [specs, , selected] = await this.selectedFiles(ctx, paths);
|
|
1218
|
+
const [diff, truncated] = await this.selectedDiffText(ctx, specs);
|
|
1114
1219
|
if (!diff.trim())
|
|
1115
1220
|
throw new GitWorkspaceError('No selected diff is available');
|
|
1116
|
-
const status = this.status(workspace);
|
|
1221
|
+
const status = await this.status(workspace);
|
|
1117
1222
|
const userPrompt = `Write a commit message for the selected Git diff below.\n\nBranch: ${status.branch || 'HEAD'}\nSelected files (${selected.length}):\n${GitRunner.fileLines(selected, 'selected').join('\n')}${truncated ? '\n\nDiff was truncated for size; summarize only what is visible.\n' : '\n'}\nSelected diff:\n\`\`\`diff\n${diff}\n\`\`\``;
|
|
1118
1223
|
return { system_prompt: COMMIT_MESSAGE_SYSTEM_PROMPT, user_prompt: userPrompt, truncated, status };
|
|
1119
1224
|
}
|
|
1120
|
-
stagedCommitMessagePrompt(workspace) {
|
|
1121
|
-
const ctx = this.resolveContext(workspace);
|
|
1225
|
+
async stagedCommitMessagePrompt(workspace) {
|
|
1226
|
+
const ctx = await this.resolveContext(workspace);
|
|
1122
1227
|
if (!ctx)
|
|
1123
1228
|
throw new GitWorkspaceError('Workspace is not a Git repository');
|
|
1124
|
-
const status = this.status(workspace);
|
|
1229
|
+
const status = await this.status(workspace);
|
|
1125
1230
|
if ((status.totals?.staged ?? 0) <= 0)
|
|
1126
1231
|
throw new GitWorkspaceError('Stage changes before generating a commit message');
|
|
1127
|
-
const [diff, truncated] = this.stagedDiffText(ctx);
|
|
1232
|
+
const [diff, truncated] = await this.stagedDiffText(ctx);
|
|
1128
1233
|
if (!diff.trim())
|
|
1129
1234
|
throw new GitWorkspaceError('No staged diff is available');
|
|
1130
1235
|
const stagedFiles = (status.files ?? []).filter((f) => f.staged);
|
|
@@ -1136,72 +1241,74 @@ export class GitRunner {
|
|
|
1136
1241
|
const msg = str(message).trim();
|
|
1137
1242
|
if (!msg)
|
|
1138
1243
|
throw new GitWorkspaceError('Commit message is required');
|
|
1139
|
-
const ctx = this.requireContext(workspace);
|
|
1140
|
-
|
|
1141
|
-
|
|
1244
|
+
const ctx = await this.requireContext(workspace);
|
|
1245
|
+
// The SHA and status are read under the lock so a later mutation's state cannot be reported as this commit's.
|
|
1246
|
+
return await this.withMutationLock(ctx, async () => {
|
|
1247
|
+
await this.run(ctx, ['commit', '-m', msg], { timeoutMs: 10_000, check: true, destructive: true, disableFilterAttributes: true });
|
|
1248
|
+
const sha = (await this.run(ctx, ['rev-parse', '--short', 'HEAD'], { check: true })).stdout.trim();
|
|
1249
|
+
return { ok: true, commit: sha, status: await this.status(workspace) };
|
|
1142
1250
|
});
|
|
1143
|
-
const sha = this.run(ctx, ['rev-parse', '--short', 'HEAD'], { check: true }).stdout.trim();
|
|
1144
|
-
return { ok: true, commit: sha, status: this.status(workspace) };
|
|
1145
1251
|
}
|
|
1146
1252
|
async commitSelected(workspace, message, paths) {
|
|
1147
1253
|
const msg = str(message).trim();
|
|
1148
1254
|
if (!msg)
|
|
1149
1255
|
throw new GitWorkspaceError('Commit message is required');
|
|
1150
|
-
const ctx = this.requireContext(workspace);
|
|
1151
|
-
|
|
1152
|
-
const [specs, wsPaths] = this.selectedFiles(ctx, paths);
|
|
1153
|
-
const [env, dir] = this.selectedTempIndexEnv(ctx, specs);
|
|
1256
|
+
const ctx = await this.requireContext(workspace);
|
|
1257
|
+
return await this.withMutationLock(ctx, async () => {
|
|
1258
|
+
const [specs, wsPaths] = await this.selectedFiles(ctx, paths);
|
|
1259
|
+
const [env, dir] = await this.selectedTempIndexEnv(ctx, specs);
|
|
1154
1260
|
try {
|
|
1155
|
-
const quiet = this.run(ctx, ['diff', '--cached', '--quiet', '--no-textconv', '--', ...specs], { env, destructive: true, disableFilterAttributes: true });
|
|
1261
|
+
const quiet = await this.run(ctx, ['diff', '--cached', '--quiet', '--no-textconv', '--', ...specs], { env, destructive: true, disableFilterAttributes: true });
|
|
1156
1262
|
if (quiet.status === 0)
|
|
1157
1263
|
throw new GitWorkspaceError('Selected paths have no committable changes');
|
|
1158
|
-
this.run(ctx, ['commit', '-m', msg], { timeoutMs: 10_000, check: true, env, destructive: true, disableFilterAttributes: true });
|
|
1159
|
-
this.run(ctx, ['reset', '-q', 'HEAD', '--', ...specs], { check: true, destructive: true });
|
|
1264
|
+
await this.run(ctx, ['commit', '-m', msg], { timeoutMs: 10_000, check: true, env, destructive: true, disableFilterAttributes: true });
|
|
1265
|
+
await this.run(ctx, ['reset', '-q', 'HEAD', '--', ...specs], { check: true, destructive: true });
|
|
1160
1266
|
}
|
|
1161
1267
|
finally {
|
|
1162
1268
|
rmSync(dir, { recursive: true, force: true });
|
|
1163
1269
|
}
|
|
1164
|
-
|
|
1270
|
+
const sha = (await this.run(ctx, ['rev-parse', '--short', 'HEAD'], { check: true })).stdout.trim();
|
|
1271
|
+
return { ok: true, commit: sha, paths: wsPaths, status: await this.status(workspace) };
|
|
1165
1272
|
});
|
|
1166
|
-
const sha = this.run(ctx, ['rev-parse', '--short', 'HEAD'], { check: true }).stdout.trim();
|
|
1167
|
-
return { ok: true, commit: sha, paths: workspacePaths, status: this.status(workspace) };
|
|
1168
1273
|
}
|
|
1169
|
-
branchName(ctx) {
|
|
1170
|
-
const branch = this.run(ctx, ['branch', '--show-current'], { check: true }).stdout.trim();
|
|
1274
|
+
async branchName(ctx) {
|
|
1275
|
+
const branch = (await this.run(ctx, ['branch', '--show-current'], { check: true })).stdout.trim();
|
|
1171
1276
|
if (!branch)
|
|
1172
1277
|
throw new GitWorkspaceError('Cannot push from a detached HEAD');
|
|
1173
1278
|
return branch;
|
|
1174
1279
|
}
|
|
1175
1280
|
async fetch(workspace) {
|
|
1176
|
-
const ctx = this.requireContext(workspace);
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1281
|
+
const ctx = await this.requireContext(workspace);
|
|
1282
|
+
return await this.withMutationLock(ctx, async () => {
|
|
1283
|
+
const result = await this.run(ctx, ['fetch', '--prune', '--no-recurse-submodules'], {
|
|
1284
|
+
timeoutMs: GIT_REMOTE_TIMEOUT_MS, check: true, forceDestructiveHardening: true, disableFilterAttributes: this.destructiveEnabled(), neutralizeFilterPrograms: true, neutralizeRemoteHelpers: true,
|
|
1285
|
+
});
|
|
1286
|
+
return { ok: true, message: GitRunner.remoteMessage(result), status: await this.status(workspace) };
|
|
1287
|
+
}, { holdRepo: false });
|
|
1181
1288
|
}
|
|
1182
1289
|
async pull(workspace) {
|
|
1183
|
-
const ctx = this.requireContext(workspace);
|
|
1184
|
-
|
|
1185
|
-
this.blockFilteredDestructiveWrite(ctx, 'Repository uses local Git filters; pull may corrupt working-tree content. Use the terminal to pull manually.');
|
|
1186
|
-
|
|
1290
|
+
const ctx = await this.requireContext(workspace);
|
|
1291
|
+
return await this.withMutationLock(ctx, async () => {
|
|
1292
|
+
await this.blockFilteredDestructiveWrite(ctx, 'Repository uses local Git filters; pull may corrupt working-tree content. Use the terminal to pull manually.');
|
|
1293
|
+
const result = await this.run(ctx, ['pull', '--ff-only', '--no-recurse-submodules'], { timeoutMs: GIT_REMOTE_TIMEOUT_MS, check: true, destructive: true, disableFilterAttributes: true, neutralizeFilterPrograms: true, neutralizeRemoteHelpers: true });
|
|
1294
|
+
return { ok: true, message: GitRunner.remoteMessage(result), status: await this.status(workspace) };
|
|
1187
1295
|
});
|
|
1188
|
-
return { ok: true, message: GitRunner.remoteMessage(result), status: this.status(workspace) };
|
|
1189
1296
|
}
|
|
1190
1297
|
async push(workspace) {
|
|
1191
|
-
const ctx = this.requireContext(workspace);
|
|
1192
|
-
|
|
1193
|
-
const status = this.status(workspace);
|
|
1298
|
+
const ctx = await this.requireContext(workspace);
|
|
1299
|
+
return await this.withMutationLock(ctx, async () => {
|
|
1300
|
+
const status = await this.status(workspace);
|
|
1194
1301
|
const args = ['push'];
|
|
1195
1302
|
if (!status.upstream) {
|
|
1196
|
-
const branch = this.branchName(ctx);
|
|
1197
|
-
const remotes = this.run(ctx, ['remote'], { check: true }).stdout.split(/\s+/).filter(Boolean);
|
|
1303
|
+
const branch = await this.branchName(ctx);
|
|
1304
|
+
const remotes = (await this.run(ctx, ['remote'], { check: true })).stdout.split(/\s+/).filter(Boolean);
|
|
1198
1305
|
if (!remotes.includes('origin'))
|
|
1199
1306
|
throw new GitWorkspaceError('No upstream branch or origin remote is configured', 'no_upstream');
|
|
1200
1307
|
args.push('-u', 'origin', branch);
|
|
1201
1308
|
}
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1309
|
+
const result = await this.run(ctx, args, { timeoutMs: GIT_REMOTE_TIMEOUT_MS, check: true, destructive: true, disableFilterAttributes: true, neutralizeFilterPrograms: true, neutralizeRemoteHelpers: true });
|
|
1310
|
+
return { ok: true, message: GitRunner.remoteMessage(result), status: await this.status(workspace) };
|
|
1311
|
+
}, { holdRepo: false });
|
|
1205
1312
|
}
|
|
1206
1313
|
}
|
|
1207
1314
|
export const COMMIT_MESSAGE_SYSTEM_PROMPT = `When writing commit messages, PR titles, or PR descriptions:
|