@organizaone/o1-code 0.4.0 → 0.5.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/bundled/qc-helper/docs/configuration/settings.md +1 -0
- package/bundled/qc-helper/docs/features/approval-mode.md +4 -0
- package/bundled/qc-helper/docs/features/auto-mode.md +3 -0
- package/bundled/qc-helper/docs/features/sub-agents.md +7 -8
- package/bundled/qc-helper/docs/support/troubleshooting.md +3 -0
- package/chunks/{MaxSizedBox-XCALBLMI.js → MaxSizedBox-ZOFIKGJG.js} +41 -41
- package/chunks/{StandaloneSessionPicker-4LKUBXYB.js → StandaloneSessionPicker-6S2C7YZK.js} +54 -54
- package/chunks/{acp-subagent-executor-DZXFG4X5.js → acp-subagent-executor-HGIGYENO.js} +42 -42
- package/chunks/{acpAgent-Q7ZCQ435.js → acpAgent-SXUNXWWP.js} +425 -368
- package/chunks/{agent-RMFRDIO3.js → agent-7GI5MAMY.js} +25 -25
- package/chunks/{agent-headless-S5VQMQW5.js → agent-headless-7ZSKSLIE.js} +25 -25
- package/chunks/{anthropicContentGenerator-FJGXD34B.js → anthropicContentGenerator-FHJ5JNYR.js} +5 -5
- package/chunks/{bridge-MPG5WH6G.js → bridge-5YHUATCB.js} +42 -42
- package/chunks/{build-F5EUF23Y.js → build-RFEU7TIS.js} +1 -1
- package/chunks/{bwrap-execution-EZJNABHL.js → bwrap-execution-QFG5RQSP.js} +5 -5
- package/chunks/{chunk-M4RMR6BN.js → chunk-22SKH7LF.js} +3 -3
- package/chunks/{chunk-MKUV6LDU.js → chunk-26GB3MTV.js} +2 -2
- package/chunks/{chunk-L2YHATCP.js → chunk-2DNMFYMU.js} +3 -3
- package/chunks/{chunk-7K3C4CS2.js → chunk-2J53EWTP.js} +6 -6
- package/chunks/{chunk-PU3SYVQ7.js → chunk-2OQC7O74.js} +3 -3
- package/chunks/{chunk-EPSMNIG6.js → chunk-2QN2TID2.js} +5 -5
- package/chunks/{chunk-UUTO4CEV.js → chunk-34ORCXAX.js} +2 -2
- package/chunks/{chunk-EXWAECKM.js → chunk-35RKSP4A.js} +2 -2
- package/chunks/{chunk-7YG72VGA.js → chunk-3GGCMM7C.js} +2 -2
- package/chunks/{chunk-S74WJTCG.js → chunk-3YLEHHA7.js} +12 -3
- package/chunks/{chunk-SQQVJC75.js → chunk-3YZPY6YA.js} +1 -1
- package/chunks/{chunk-UDQ3FEOE.js → chunk-6DOSH2VF.js} +30 -15
- package/chunks/{chunk-ESAUSKWS.js → chunk-6LOAUBTH.js} +10 -10
- package/chunks/{chunk-ESDBDHDS.js → chunk-6MEAGROF.js} +1 -1
- package/chunks/{chunk-UIZPA6NX.js → chunk-6QYP53HJ.js} +1 -1
- package/chunks/{chunk-KXN7Q6SB.js → chunk-6TWJYLJE.js} +1 -1
- package/chunks/{chunk-Q56YIKDZ.js → chunk-734USTD3.js} +5 -5
- package/chunks/{chunk-TY4PWRCX.js → chunk-7PXVBJL3.js} +1 -1
- package/chunks/{chunk-CIMPNO36.js → chunk-7TZRKZWC.js} +1 -1
- package/chunks/{chunk-7NLIWXMU.js → chunk-A2K4A5O2.js} +3 -3
- package/chunks/{chunk-672SUHHG.js → chunk-ABTF7TPE.js} +1 -1
- package/chunks/{chunk-FOVK2DPM.js → chunk-AJS6HD3U.js} +4 -4
- package/chunks/{chunk-PUHCGRAC.js → chunk-B2PLLGOC.js} +1 -1
- package/chunks/{chunk-WEL3TXQG.js → chunk-B67EYF6M.js} +1 -1
- package/chunks/{chunk-PIVKM7A6.js → chunk-C2IESIWR.js} +4 -4
- package/chunks/{chunk-X3RSD63L.js → chunk-CHZDN4DI.js} +1 -1
- package/chunks/{chunk-S2LBLNBW.js → chunk-CMZHL55Y.js} +2193 -151
- package/chunks/{chunk-A53V5RUQ.js → chunk-CVU3EIFH.js} +3 -3
- package/chunks/{chunk-HYC64HT7.js → chunk-D42MFAOX.js} +3 -3
- package/chunks/{chunk-MW6HQT5X.js → chunk-DLJHG562.js} +2 -2
- package/chunks/{chunk-YNJSBU2B.js → chunk-DZPGLX7M.js} +1 -1
- package/chunks/{chunk-GJQHQ3DC.js → chunk-E6JZE22X.js} +3 -3
- package/chunks/{chunk-YP7V6HRD.js → chunk-EBTQTM56.js} +1 -1
- package/chunks/{chunk-LWQQFURM.js → chunk-ELAUQQIT.js} +4 -4
- package/chunks/{chunk-WXWA7OOJ.js → chunk-ELVKWFRK.js} +1 -1
- package/chunks/{chunk-SAMGM2NV.js → chunk-EMIGZJZT.js} +1 -1
- package/chunks/{chunk-DM3OSSGT.js → chunk-F6UT36OB.js} +7 -42
- package/chunks/{chunk-T233J2WC.js → chunk-FMDABBCA.js} +3 -3
- package/chunks/{chunk-P7UNPKJM.js → chunk-FQO3QDB5.js} +1 -1
- package/chunks/{chunk-TKHMBUSK.js → chunk-FZUEC7ES.js} +3 -3
- package/chunks/{chunk-L3LXMQJJ.js → chunk-G2I75T4C.js} +1 -1
- package/chunks/{chunk-DP5BYVKX.js → chunk-G3W4UY3G.js} +3601 -5403
- package/chunks/{chunk-HRWSTIHZ.js → chunk-GAM5VJT7.js} +1 -1
- package/chunks/{chunk-5L6KVN6H.js → chunk-GHYY2N2S.js} +1 -1
- package/chunks/{chunk-LQR2LD5J.js → chunk-GLT2QUV7.js} +4 -4
- package/chunks/{chunk-ONLJONFH.js → chunk-GMR3LAWE.js} +18 -3
- package/chunks/{chunk-FXEUBHXO.js → chunk-GN6HVKBI.js} +2 -2
- package/chunks/{chunk-532CXTUJ.js → chunk-GW3TK3X2.js} +39 -37
- package/chunks/{chunk-V5LAES7R.js → chunk-GWX3EC6J.js} +1 -1
- package/chunks/{chunk-E4BNWSC7.js → chunk-H5L5E5NI.js} +1 -1
- package/chunks/{chunk-CVGR5FZZ.js → chunk-H65LZ7Q4.js} +1 -1
- package/chunks/{chunk-AGF6RZYW.js → chunk-HGW4END7.js} +4 -4
- package/chunks/{chunk-PAHN3PPP.js → chunk-IL4VUEEH.js} +1 -1
- package/chunks/{chunk-FJHPFQU6.js → chunk-JDPRCTE4.js} +7 -7
- package/chunks/{chunk-4NNLMYE3.js → chunk-JOGRE2ZT.js} +1 -1
- package/chunks/{chunk-IFURAKKH.js → chunk-JSB66IE5.js} +1 -1
- package/chunks/{chunk-IF63JJ4W.js → chunk-K5JXFVNB.js} +5 -5
- package/chunks/{chunk-AOXGYW3S.js → chunk-KCHIL32J.js} +5 -5
- package/chunks/{chunk-QGDWW62J.js → chunk-KI7AYT4Y.js} +1 -1
- package/chunks/{chunk-JHKNFNWE.js → chunk-KJVLRZBK.js} +1 -1
- package/chunks/{chunk-WHFNK466.js → chunk-KVQMH7W2.js} +4 -4
- package/chunks/{chunk-ROJAAA2B.js → chunk-L6J57N2A.js} +1 -1
- package/chunks/{chunk-FD5MGAM2.js → chunk-L7LXG6TN.js} +2 -2
- package/chunks/{chunk-L4RFUGED.js → chunk-LERXWKRT.js} +1 -1
- package/chunks/{chunk-KFYFHLDM.js → chunk-LHCVJ6NV.js} +1 -1
- package/chunks/{chunk-Q2AHXMRI.js → chunk-LHY2PDWX.js} +6 -4
- package/chunks/{chunk-IUFQFQ7R.js → chunk-LJU6OPEU.js} +3 -3
- package/chunks/{chunk-YIEC5SIW.js → chunk-M2EBVYXC.js} +1 -1
- package/chunks/{chunk-DRV3SDND.js → chunk-M5G6OKEY.js} +4 -4
- package/chunks/{chunk-5TJZ5DBW.js → chunk-MDQFX2C5.js} +1 -1
- package/chunks/{chunk-WQ6N4SRY.js → chunk-MDQYCFNT.js} +16 -16
- package/chunks/{chunk-5PZDTHS6.js → chunk-MGVVXFYW.js} +38 -3
- package/chunks/{chunk-JQFQB2BI.js → chunk-MIWMCUU5.js} +1 -1
- package/chunks/{chunk-XID74DEW.js → chunk-MQ3QD65Z.js} +1 -1
- package/chunks/{chunk-KCOLW7TS.js → chunk-MRBPTAOV.js} +4 -4
- package/chunks/{chunk-5KDZJN3L.js → chunk-MRJJAG44.js} +10 -10
- package/chunks/{chunk-WB4XHWF5.js → chunk-MX7QDQRT.js} +5 -5
- package/chunks/{chunk-6ET4INUP.js → chunk-MXIQ6C7B.js} +3 -3
- package/chunks/{chunk-VRSR6QUT.js → chunk-NBCYYKYK.js} +3 -3
- package/chunks/{chunk-I7XXUDXZ.js → chunk-NGPEFWU4.js} +1 -1
- package/chunks/{chunk-YNDEPL5B.js → chunk-ODSVNOLT.js} +4 -4
- package/chunks/{chunk-AQVYFR7E.js → chunk-OMJU4YIQ.js} +1 -1
- package/chunks/{chunk-TXOO3SJ4.js → chunk-OVOSIR5V.js} +13 -11
- package/chunks/{chunk-4372PUEO.js → chunk-PSL5VFNJ.js} +40 -37
- package/chunks/{chunk-UNRHVAPY.js → chunk-QDZTSZTF.js} +1 -1
- package/chunks/{chunk-XQILGSD3.js → chunk-RIX6ATSB.js} +1 -1
- package/chunks/{chunk-T2OY4BYB.js → chunk-RRWM6VZS.js} +1 -1
- package/chunks/{chunk-W7IBLOA5.js → chunk-SE33PFPT.js} +7 -7
- package/chunks/{chunk-H2V7AUF4.js → chunk-SE3HMQLW.js} +5 -5
- package/chunks/{chunk-QFQQICSQ.js → chunk-TCME6IG6.js} +1 -1
- package/chunks/{chunk-RWN5ULQM.js → chunk-TDBZBCLU.js} +2 -2
- package/chunks/{chunk-IYHIP3OB.js → chunk-TXIBLU7T.js} +8 -8
- package/chunks/{chunk-Y4BQMESG.js → chunk-TYAUV2TE.js} +1 -1
- package/chunks/{chunk-3QL5SP6K.js → chunk-UZSRQ26K.js} +1 -1
- package/chunks/{chunk-J5BDFZYA.js → chunk-V23Q4GBY.js} +1 -1
- package/chunks/{chunk-L3YLV3RI.js → chunk-V4XETRAG.js} +24 -9
- package/chunks/{chunk-PPR5SHEF.js → chunk-V75J4GJ2.js} +1 -1
- package/chunks/{chunk-QLDNOCCI.js → chunk-VJ3EJJ5K.js} +4 -4
- package/chunks/{chunk-QJVAY275.js → chunk-W2MWOPYK.js} +13 -10
- package/chunks/{chunk-KD2JFDCT.js → chunk-WBW6J5R3.js} +13 -4
- package/chunks/{chunk-PYH6MQ2Q.js → chunk-WCFNOALO.js} +1 -1
- package/chunks/{chunk-A24OKQOD.js → chunk-WFUXHQZO.js} +6 -2
- package/chunks/{chunk-KN233J25.js → chunk-WQNNHDDX.js} +6 -6
- package/chunks/{chunk-X6HNUJYB.js → chunk-X655UILS.js} +15 -15
- package/chunks/{chunk-RTY3POZ7.js → chunk-XFV6QC4A.js} +2 -2
- package/chunks/{chunk-GUMLL5KC.js → chunk-XKOGHPWS.js} +1 -1
- package/chunks/{chunk-WBXFLYVH.js → chunk-YQF62HFY.js} +1 -1
- package/chunks/{chunk-GWKSWZ3B.js → chunk-Z6HFYF3D.js} +1 -1
- package/chunks/{chunk-U6PVPH4S.js → chunk-ZFT5MBI6.js} +1 -1
- package/chunks/{chunk-M2WG4ESD.js → chunk-ZQBNP2ZQ.js} +4 -2
- package/chunks/{chunk-JPGOF34S.js → chunk-ZZXUYKAG.js} +4 -4
- package/chunks/{codex-subagent-executor-E7JQVLPC.js → codex-subagent-executor-NO5C6VPN.js} +42 -42
- package/chunks/{contextCommand-7L5H2XXP.js → contextCommand-VWARZSMR.js} +40 -40
- package/chunks/{core-runtime-X3V7BURU.js → core-runtime-KL6UEQWI.js} +39 -39
- package/chunks/{create-sub-session-VXPEZMFK.js → create-sub-session-CY7EFJX5.js} +40 -40
- package/chunks/{daemon-git-worktree-guard-OYFVIIWI.js → daemon-git-worktree-guard-CPIBCRZO.js} +39 -39
- package/chunks/{daemon-status-provider-5QIZJJNE.js → daemon-status-provider-BN3RF2FL.js} +47 -47
- package/chunks/{daemon-trust-policy-LVRILVI3.js → daemon-trust-policy-4CK5U5IC.js} +44 -44
- package/chunks/{daemon-trust-policy-monitor-GI6PLXY4.js → daemon-trust-policy-monitor-CUNFNI4N.js} +44 -44
- package/chunks/{deferred-core-runtime-6CBEDR5U.js → deferred-core-runtime-CBJTZJPH.js} +39 -39
- package/chunks/{display-image-K4EKCY4Y.js → display-image-U6HZKF3G.js} +1 -1
- package/chunks/{edit-C6FPBRWV.js → edit-RM227QNR.js} +27 -27
- package/chunks/{enter-worktree-7AEF4YHW.js → enter-worktree-53CUMISV.js} +6 -5
- package/chunks/{enterPlanMode-MNSV64QE.js → enterPlanMode-PYAHJFSQ.js} +25 -25
- package/chunks/{environment-UHBXJNPE.js → environment-DD54IMFM.js} +45 -43
- package/chunks/{errors-N36MS36S.js → errors-HI6DJG4B.js} +41 -41
- package/chunks/{execution-sandbox-config-ODVPW3HT.js → execution-sandbox-config-7UDTKDKF.js} +6 -6
- package/chunks/{execution-tool-CB46DCPG.js → execution-tool-V3VSCW6K.js} +28 -28
- package/chunks/{execution-worker-KGQ3AKWY.js → execution-worker-WBHEON6Q.js} +32 -32
- package/chunks/{exit-worktree-7LRFLHHT.js → exit-worktree-CZUYCPZ3.js} +6 -5
- package/chunks/{exitPlanMode-XQICYHTC.js → exitPlanMode-HOZQZ5T7.js} +25 -25
- package/chunks/{fast-path-YHYEVNSH.js → fast-path-7HGISECB.js} +2 -2
- package/chunks/{glob-QZN6HURE.js → glob-3Z2KONQG.js} +25 -25
- package/chunks/{goal-tools-DA6LG2SP.js → goal-tools-SZZFOYEQ.js} +25 -25
- package/chunks/{grep-TA3S572F.js → grep-ENZEP6KL.js} +2 -2
- package/chunks/{handleAutoUpdate-IGXZ2DMP.js → handleAutoUpdate-YW4VDPGO.js} +41 -41
- package/chunks/{i18n-IALGY42H.js → i18n-3EQ4CCDM.js} +39 -39
- package/chunks/{image-gen-BEM2OTJM.js → image-gen-GJRWPVTR.js} +2 -2
- package/chunks/{initializer-ZIHB4XYC.js → initializer-G4SCU6JQ.js} +44 -44
- package/chunks/{installationInfo-WG2GZFZT.js → installationInfo-3QFLR7CO.js} +39 -39
- package/chunks/{list-RZD6J74J.js → list-S4KQBUXO.js} +46 -46
- package/chunks/{llm-46DCEZ2L.js → llm-HBFVSHNO.js} +95 -73
- package/chunks/{llm-content-generator-OT4NV35N.js → llm-content-generator-WWIIJ3YF.js} +3 -3
- package/chunks/{loadedSettingsAdapter-J2GSJPSR.js → loadedSettingsAdapter-4FCPSPON.js} +44 -44
- package/chunks/{local-execution-environment-FX5IZ7Z2.js → local-execution-environment-PUSM6PGS.js} +32 -32
- package/chunks/{loggingContentGenerator-RTFUIXIY.js → loggingContentGenerator-OMYQPXBM.js} +27 -27
- package/chunks/{managed-npm-update-4FBPYKRR.js → managed-npm-update-MUWFIXMB.js} +56 -50
- package/chunks/{mcp-CGZQMURL.js → mcp-IFIMZMMB.js} +44 -44
- package/chunks/{model-call-collection-XSB3IOIM.js → model-call-collection-RJ3QGO3O.js} +1 -1
- package/chunks/{model-configuration-7XGFDFQQ.js → model-configuration-AQOY5NHG.js} +45 -45
- package/chunks/{modelConfigUtils-55SLUTRW.js → modelConfigUtils-3WCZU5US.js} +41 -41
- package/chunks/{monitor-6FXANYQK.js → monitor-Z4DAPTBI.js} +40 -36
- package/chunks/{nonInteractiveCli-7JRQVPTS.js → nonInteractiveCli-EIFDEYFS.js} +70 -70
- package/chunks/{nonInteractiveToolExecutor-EBI7JPSA.js → nonInteractiveToolExecutor-NS2HFUO5.js} +26 -26
- package/chunks/{notebook-edit-4NRLVXGD.js → notebook-edit-TXNVV4NZ.js} +26 -26
- package/chunks/{omni-SCKLBJ7D.js → omni-EANTWQG5.js} +10 -10
- package/chunks/{openaiContentGenerator-5XUSGWXX.js → openaiContentGenerator-JU5KLG5T.js} +12 -12
- package/chunks/{openaiResponsesContentGenerator-XKJFDUNK.js → openaiResponsesContentGenerator-BQZ54E6V.js} +3 -3
- package/chunks/{processUtils-RZJXB2SU.js → processUtils-PVRINBPF.js} +2 -2
- package/chunks/{prompt-terminal-ledger-WDNYXW7Q.js → prompt-terminal-ledger-LDPMNEHI.js} +40 -40
- package/chunks/{reactive-degrade-NS277SXZ.js → reactive-degrade-5HVNIKLO.js} +8 -8
- package/chunks/{read-file-7A4PDVYW.js → read-file-H4L4HPEH.js} +4 -4
- package/chunks/{record-artifact-LO3TFJF4.js → record-artifact-PL2T5AJY.js} +3 -3
- package/chunks/{record-source-U2PL3PZS.js → record-source-PHBQTGL4.js} +4 -4
- package/chunks/{report-findings-XQQ23B4H.js → report-findings-RFO53756.js} +4 -4
- package/chunks/{request-shutdown-HUO3DW4O.js → request-shutdown-OMWHNC3R.js} +3 -3
- package/chunks/{resumeHistoryUtils-TQIL6EZ7.js → resumeHistoryUtils-TMGVZMKJ.js} +41 -41
- package/chunks/{ripGrep-APKWY2LV.js → ripGrep-YVUC4DXT.js} +7 -7
- package/chunks/{run-o1-code-serve-ZPJZEWP7.js → run-o1-code-serve-LAIBSEHT.js} +32 -32
- package/chunks/{runtime-shell-policy-7E34X54Z.js → runtime-shell-policy-QXWSET5W.js} +1 -1
- package/chunks/{sandboxConfig-FJ6SORCO.js → sandboxConfig-UYUFPT6T.js} +39 -39
- package/chunks/{scheduled-tasks-K5FEKEQZ.js → scheduled-tasks-JFRIRKTA.js} +45 -45
- package/chunks/{scheduler-LNBDPLM3.js → scheduler-2PWRO4UB.js} +41 -41
- package/chunks/{send-message-X55LV3NW.js → send-message-NATKTKQZ.js} +3 -3
- package/chunks/{serve-YLTHY7B3.js → serve-5NOYAIKF.js} +44 -44
- package/chunks/{server-246IXJV4.js → server-VJ4WGSUY.js} +119 -111
- package/chunks/{session-U5PCPVSR.js → session-CGV7TJGH.js} +71 -71
- package/chunks/{session-attachments-root-FZBDWCCJ.js → session-attachments-root-COVGVQZC.js} +39 -39
- package/chunks/{session-pr-refresh-G4DYR2GA.js → session-pr-refresh-SX33MGB4.js} +44 -44
- package/chunks/{settings-5CSMP2XC.js → settings-XRNF45NA.js} +43 -43
- package/chunks/{shell-MHNR7ELX.js → shell-RX3LXNJP.js} +25 -25
- package/chunks/{skill-BQH2WIPE.js → skill-KCPU5AV6.js} +5 -5
- package/chunks/{skill-settings-NBLEU6N3.js → skill-settings-PAEWMBXK.js} +44 -44
- package/chunks/{spawnChannel-OWXPYFWT.js → spawnChannel-SNCSRXCH.js} +41 -41
- package/chunks/{standalone-update-U6UUKRKU.js → standalone-update-WPASUR47.js} +40 -40
- package/chunks/{startInteractiveUI-EJULGCL7.js → startInteractiveUI-ZX3UUKMI.js} +104 -87
- package/chunks/{task-create-A2MP37QH.js → task-create-A3FAT34J.js} +4 -4
- package/chunks/{task-update-NJG2RTP7.js → task-update-BE27HYCU.js} +4 -4
- package/chunks/{team-create-PJK2OS5B.js → team-create-LV77LY5U.js} +25 -25
- package/chunks/{team-plan-approval-KSJV7ZQ6.js → team-plan-approval-VBIL6SV3.js} +25 -25
- package/chunks/{terminal-image-renderer-3K262PTL.js → terminal-image-renderer-ADP6QUHM.js} +39 -39
- package/chunks/{theme-manager-JR5EN3JA.js → theme-manager-EUUSF2Q7.js} +39 -39
- package/chunks/{tool-call-VNMKD4BX.js → tool-call-7HZFNB7J.js} +4 -4
- package/chunks/{tool-result-media-5G5L5FUD.js → tool-result-media-JVU7TVUQ.js} +10 -10
- package/chunks/{tool-search-RMXHOHI6.js → tool-search-PEJVNWOB.js} +7 -7
- package/chunks/{total-session-admission-DNSWPKQR.js → total-session-admission-N7ME7DLF.js} +42 -42
- package/chunks/{trustedFolders-MXRKCC7V.js → trustedFolders-G3EG6RNC.js} +40 -40
- package/chunks/{update-relaunch-YZ752NJD.js → update-relaunch-OAYGBIDB.js} +17 -8
- package/chunks/{updateCheck-5B4SBAZT.js → updateCheck-JKYZNZTK.js} +39 -39
- package/chunks/{upload-config-CMV4ABF2.js → upload-config-LPONQVLM.js} +6 -6
- package/chunks/{useAutoAcceptIndicator-UVPPBAVQ.js → useAutoAcceptIndicator-AZBWWHTT.js} +47 -47
- package/chunks/{validateNonInterActiveAuth-Y55S3BD5.js → validateNonInterActiveAuth-5K6TQOCW.js} +68 -68
- package/chunks/{version-G4JNJAHP.js → version-7MOVE5JZ.js} +1 -1
- package/chunks/{web-fetch-GMZV2QHR.js → web-fetch-ALDHPFKB.js} +5 -5
- package/chunks/{web-search-C74YSUHS.js → web-search-4FXUXV76.js} +7 -7
- package/chunks/{workflow-QNJRLQVY.js → workflow-W6TQFKWS.js} +26 -26
- package/chunks/{workspace-providers-status-NQJJBMZF.js → workspace-providers-status-JRU6E7KT.js} +50 -50
- package/chunks/{workspace-registration-store-SFC3KHYO.js → workspace-registration-store-2VLHZRVP.js} +1 -1
- package/chunks/{workspace-registry-N5DD5XIM.js → workspace-registry-BYB7WBWO.js} +42 -42
- package/chunks/{workspace-runtime-coordinator-BXYGAFZC.js → workspace-runtime-coordinator-EEAZTJ6X.js} +43 -43
- package/chunks/{workspace-service-ZVV75GHH.js → workspace-service-7SCTZ2HS.js} +48 -48
- package/chunks/{workspace-skills-status-ZTULNHRY.js → workspace-skills-status-HDLEGK3V.js} +47 -47
- package/chunks/{workspace-trust-reconciler-FFQBYGN6.js → workspace-trust-reconciler-SONZ46YA.js} +47 -47
- package/chunks/{write-file-VLVTXBBH.js → write-file-BIRFPH4F.js} +28 -28
- package/chunks/{zoom-image-RCU2ZECA.js → zoom-image-UFU3VEIY.js} +4 -4
- package/cli-entry.js +35 -18
- package/cli.js +12 -12
- package/execution-worker.js +1 -1
- package/export-transcript-document.js +1 -1
- package/package.json +2 -2
- package/release-notes.json +31 -0
- package/web-shell/assets/{abnfDiagram-VCTEODGH-C79vZf7q.js → abnfDiagram-VCTEODGH-BvoMbUn4.js} +1 -1
- package/web-shell/assets/{arc-By9TNVXj.js → arc-DbQBncMt.js} +1 -1
- package/web-shell/assets/{architectureDiagram-5GKGNRK7-dOGgroC-.js → architectureDiagram-5GKGNRK7-C8RG0dgJ.js} +1 -1
- package/web-shell/assets/{blockDiagram-NRAW4CY4-B9dT448v.js → blockDiagram-NRAW4CY4-pL2S69WR.js} +1 -1
- package/web-shell/assets/{c4Diagram-UCG6FXSJ-BZq7QGRh.js → c4Diagram-UCG6FXSJ-BtqW-kE-.js} +1 -1
- package/web-shell/assets/channel-D6P_9m4V.js +1 -0
- package/web-shell/assets/{chunk-2Q5K7J3B-Ddaq9Ppd.js → chunk-2Q5K7J3B-DH8xQogC.js} +1 -1
- package/web-shell/assets/{chunk-5VM5RSS4-BBjSH5RT.js → chunk-5VM5RSS4-BH3myFbn.js} +1 -1
- package/web-shell/assets/{chunk-F27PBJKO-t4UEfBw5.js → chunk-F27PBJKO-iY2yWixd.js} +1 -1
- package/web-shell/assets/{chunk-G27WJ6UU-D_VuJGqI.js → chunk-G27WJ6UU-D_j7Xo_Y.js} +1 -1
- package/web-shell/assets/{chunk-JWPE2WC7-BqUwq2mP.js → chunk-JWPE2WC7-CxBsiYSx.js} +1 -1
- package/web-shell/assets/{chunk-LCL6LL3I-TJVqP4zd.js → chunk-LCL6LL3I-DQbc3B6E.js} +1 -1
- package/web-shell/assets/{chunk-POPQ4Y6H-m8_NZQnQ.js → chunk-POPQ4Y6H-BdaRtQLf.js} +1 -1
- package/web-shell/assets/{chunk-SVP7TREG-CfC75Ei3.js → chunk-SVP7TREG-D58Lphw-.js} +1 -1
- package/web-shell/assets/{chunk-XXDRQBXY-B-Q7EfLc.js → chunk-XXDRQBXY-BSmrr5ej.js} +1 -1
- package/web-shell/assets/classDiagram-DTDB5LWJ-BW3KY7XN.js +1 -0
- package/web-shell/assets/classDiagram-v2-JRS7N3AN-BW3KY7XN.js +1 -0
- package/web-shell/assets/{cose-bilkent-JH36ORCC-D1UWVn_j.js → cose-bilkent-JH36ORCC-DYv3YjD8.js} +1 -1
- package/web-shell/assets/{cynefin-OW5HDTMX-BYyvfU2W.js → cynefin-OW5HDTMX-ChRVKOIc.js} +1 -1
- package/web-shell/assets/{cynefinDiagram-5FMLGOSQ-h31WLcWO.js → cynefinDiagram-5FMLGOSQ-2dgun5lr.js} +1 -1
- package/web-shell/assets/{dagre-3AP2YEHR-CBfIQyq3.js → dagre-3AP2YEHR-IGE15SAI.js} +1 -1
- package/web-shell/assets/{diagram-S7CK7UJ4-DvhgeRNT.js → diagram-S7CK7UJ4-BHcZWynB.js} +1 -1
- package/web-shell/assets/{diagram-UQ7AKVKN-B45bX9cw.js → diagram-UQ7AKVKN-DalYrZk-.js} +1 -1
- package/web-shell/assets/{diagram-VSXAHHWV-w5_5TRwb.js → diagram-VSXAHHWV-CMKHsUs7.js} +1 -1
- package/web-shell/assets/{diagram-VX7I27RA-DAiR8vdO.js → diagram-VX7I27RA-DgrQKlmX.js} +1 -1
- package/web-shell/assets/{diagram-Z3DM3KII-Da7jZeIH.js → diagram-Z3DM3KII-CVdgT4Q4.js} +1 -1
- package/web-shell/assets/{ebnfDiagram-PWID7BFC-B7WF9Qum.js → ebnfDiagram-PWID7BFC-BV7RGD_D.js} +1 -1
- package/web-shell/assets/{erDiagram-SSCWMZ5O-Dygfopi-.js → erDiagram-SSCWMZ5O-NbSHPXJb.js} +1 -1
- package/web-shell/assets/{flowDiagram-A5DVABFB-BX7okAuU.js → flowDiagram-A5DVABFB-BnKFiusj.js} +1 -1
- package/web-shell/assets/{ganttDiagram-EL5Y4UJY-DmUOanpH.js → ganttDiagram-EL5Y4UJY-BVBViIDh.js} +1 -1
- package/web-shell/assets/{gitGraphDiagram-WWUBYQGX-DQEg-Tk1.js → gitGraphDiagram-WWUBYQGX-Dpi8eTTY.js} +1 -1
- package/web-shell/assets/{index-CvYCzWNp.js → index-D-kPo23X.js} +1 -1
- package/web-shell/assets/{index-v3wlXQpe.js → index-DtvQ3DEH.js} +3 -3
- package/web-shell/assets/{infoDiagram-RXCK75RN-C1jbM00T.js → infoDiagram-RXCK75RN-BpAv0FDb.js} +1 -1
- package/web-shell/assets/{ishikawaDiagram-5VMMS53U-cBjbNTV4.js → ishikawaDiagram-5VMMS53U-B7958rb5.js} +1 -1
- package/web-shell/assets/{journeyDiagram-EYS64GPL-BazjwXUM.js → journeyDiagram-EYS64GPL-C3KfVyTV.js} +1 -1
- package/web-shell/assets/{kanban-definition-3QL26DDD-CUfveZjf.js → kanban-definition-3QL26DDD-DXrBR6gr.js} +1 -1
- package/web-shell/assets/{layout-CtpxXAF_.js → layout-D4sWObAV.js} +1 -1
- package/web-shell/assets/{linear-VD2q0Clb.js → linear-Cwd2KGkf.js} +1 -1
- package/web-shell/assets/{mermaid.core-B6znjYEM.js → mermaid.core-B2gOqjPo.js} +6 -6
- package/web-shell/assets/{mindmap-definition-FBJOCRG2-bOsOTlkR.js → mindmap-definition-FBJOCRG2-Oxi-8t2C.js} +1 -1
- package/web-shell/assets/{pegDiagram-XKGWAZYB-iWFBUxYu.js → pegDiagram-XKGWAZYB-XhK_Omz8.js} +1 -1
- package/web-shell/assets/{pieDiagram-E7YTZNPT-BXQlXRf2.js → pieDiagram-E7YTZNPT-th4P1FI2.js} +1 -1
- package/web-shell/assets/{quadrantDiagram-AXDQQJYC-CL0aHMKX.js → quadrantDiagram-AXDQQJYC-BWHRjMh9.js} +1 -1
- package/web-shell/assets/{railroadDiagram-O6MQD6OU-D3kquTPx.js → railroadDiagram-O6MQD6OU-D4h49f-M.js} +1 -1
- package/web-shell/assets/{requirementDiagram-EFPCY7ZU-C0xj6gpA.js → requirementDiagram-EFPCY7ZU-DSG8xXCi.js} +1 -1
- package/web-shell/assets/{sankeyDiagram-P5KCCOFB-Dllh3B5D.js → sankeyDiagram-P5KCCOFB-Tt-9UBxT.js} +1 -1
- package/web-shell/assets/{sequenceDiagram-WJ2MYXX4-CMK9o17d.js → sequenceDiagram-WJ2MYXX4-BOh5x5rY.js} +1 -1
- package/web-shell/assets/{sizeCapture-X5ZJPWSS-CJ-FZ3Po.js → sizeCapture-X5ZJPWSS-DP0VY8RY.js} +1 -1
- package/web-shell/assets/{stateDiagram-HBIQ2CUA-Dk_gRN1Y.js → stateDiagram-HBIQ2CUA-Bqhxdcnc.js} +1 -1
- package/web-shell/assets/stateDiagram-v2-4QOOHH4V-X9Lr9vnN.js +1 -0
- package/web-shell/assets/{swimlanes-XN3QIQJK-DCCq_M5v.js → swimlanes-XN3QIQJK-g1wrt3v8.js} +1 -1
- package/web-shell/assets/swimlanesDiagram-VK2B7HYN-DCoSHHNU.js +8 -0
- package/web-shell/assets/{timeline-definition-24CTP7MA-D-XQqKZ2.js → timeline-definition-24CTP7MA-GGkTR6Uu.js} +1 -1
- package/web-shell/assets/{vennDiagram-4TSXK5OY-CGtjiahk.js → vennDiagram-4TSXK5OY-yDSKCBuy.js} +1 -1
- package/web-shell/assets/{wardleyDiagram-VM6X3IG4-QC0OvkzN.js → wardleyDiagram-VM6X3IG4-Cmfcjc_f.js} +1 -1
- package/web-shell/assets/{xychartDiagram-S5SC5T6Z-C-xkrwRW.js → xychartDiagram-S5SC5T6Z-B9EjOHFX.js} +1 -1
- package/web-shell/index.html +1 -1
- package/web-shell/sw.js +1 -1
- package/web-shell/assets/channel-To-6cDF_.js +0 -1
- package/web-shell/assets/classDiagram-DTDB5LWJ-DMRUneay.js +0 -1
- package/web-shell/assets/classDiagram-v2-JRS7N3AN-DMRUneay.js +0 -1
- package/web-shell/assets/stateDiagram-v2-4QOOHH4V-B4fWCuA5.js +0 -1
- package/web-shell/assets/swimlanesDiagram-VK2B7HYN-CTS3qlaP.js +0 -8
|
@@ -1,16 +1,22 @@
|
|
|
1
1
|
// Force strict mode and setup for ESM
|
|
2
2
|
"use strict";
|
|
3
|
+
import {
|
|
4
|
+
isUnverifiableIdentityError,
|
|
5
|
+
openNoFollow
|
|
6
|
+
} from "./chunk-DJOP5LOE.js";
|
|
3
7
|
import {
|
|
4
8
|
isCommandAvailable
|
|
5
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-V23Q4GBY.js";
|
|
6
10
|
import {
|
|
7
11
|
NO_EXEC_CONFIG,
|
|
8
|
-
NO_EXEC_CONFIG_SETTINGS
|
|
12
|
+
NO_EXEC_CONFIG_SETTINGS,
|
|
13
|
+
findGitRoot,
|
|
14
|
+
readFirstLineNoFollow
|
|
9
15
|
} from "./chunk-IO32UWHY.js";
|
|
10
16
|
import {
|
|
11
17
|
fileExists,
|
|
12
18
|
isWithinRoot
|
|
13
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-H5L5E5NI.js";
|
|
14
20
|
import {
|
|
15
21
|
atomicWriteFile,
|
|
16
22
|
atomicWriteJSON
|
|
@@ -121,11 +127,1963 @@ __name(isFiniteInteger, "isFiniteInteger");
|
|
|
121
127
|
// packages/core/src/services/gitWorktreeService.ts
|
|
122
128
|
init_esbuild_shims();
|
|
123
129
|
import nodeFs from "node:fs";
|
|
124
|
-
import * as
|
|
125
|
-
import * as
|
|
130
|
+
import * as fs4 from "node:fs/promises";
|
|
131
|
+
import * as path5 from "node:path";
|
|
126
132
|
import { randomBytes, randomInt } from "node:crypto";
|
|
127
|
-
import { execFile, execSync } from "node:child_process";
|
|
133
|
+
import { execFile as execFile3, execSync } from "node:child_process";
|
|
134
|
+
import { promisify as promisify3 } from "node:util";
|
|
135
|
+
|
|
136
|
+
// packages/core/src/utils/git-branches.ts
|
|
137
|
+
init_esbuild_shims();
|
|
138
|
+
import { execFile as execFile2 } from "node:child_process";
|
|
139
|
+
import * as fs3 from "node:fs";
|
|
140
|
+
import * as path4 from "node:path";
|
|
141
|
+
import { promisify as promisify2 } from "node:util";
|
|
142
|
+
|
|
143
|
+
// packages/core/src/utils/gitDirect.ts
|
|
144
|
+
init_esbuild_shims();
|
|
145
|
+
import * as fs2 from "node:fs";
|
|
146
|
+
import * as fsPromises from "node:fs/promises";
|
|
147
|
+
import * as path3 from "node:path";
|
|
148
|
+
|
|
149
|
+
// packages/core/src/utils/gitDiff.ts
|
|
150
|
+
init_esbuild_shims();
|
|
151
|
+
import { execFile } from "node:child_process";
|
|
152
|
+
import { access, lstat, open, readFile as readFile2, stat } from "node:fs/promises";
|
|
153
|
+
import * as path2 from "node:path";
|
|
128
154
|
import { promisify } from "node:util";
|
|
155
|
+
var execFileAsync = promisify(execFile);
|
|
156
|
+
var GIT_TIMEOUT_MS = 5e3;
|
|
157
|
+
var MAX_FILES = 50;
|
|
158
|
+
var MAX_DIFF_SIZE_BYTES = 1e6;
|
|
159
|
+
var MAX_LINES_PER_FILE = 400;
|
|
160
|
+
var MAX_FILES_FOR_DETAILS = 500;
|
|
161
|
+
var EMPTY_STATS = {
|
|
162
|
+
filesCount: 0,
|
|
163
|
+
linesAdded: 0,
|
|
164
|
+
linesRemoved: 0
|
|
165
|
+
};
|
|
166
|
+
var UNTRACKED_READ_CAP_BYTES = MAX_DIFF_SIZE_BYTES;
|
|
167
|
+
var UNTRACKED_READ_CHUNK_BYTES = 64 * 1024;
|
|
168
|
+
var BINARY_SNIFF_BYTES = 8 * 1024;
|
|
169
|
+
async function openUntrackedForDiffRead(absPath) {
|
|
170
|
+
try {
|
|
171
|
+
return await openNoFollow(absPath);
|
|
172
|
+
} catch (error) {
|
|
173
|
+
if (!isUnverifiableIdentityError(error)) return void 0;
|
|
174
|
+
}
|
|
175
|
+
try {
|
|
176
|
+
return await open(absPath);
|
|
177
|
+
} catch {
|
|
178
|
+
return void 0;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
__name(openUntrackedForDiffRead, "openUntrackedForDiffRead");
|
|
182
|
+
async function fetchGitDiff(cwd) {
|
|
183
|
+
const gitRoot = findGitRoot(cwd);
|
|
184
|
+
if (!gitRoot) return null;
|
|
185
|
+
if (await isInTransientGitState(gitRoot)) return null;
|
|
186
|
+
const [shortstatOut, untrackedOut] = await Promise.all([
|
|
187
|
+
runGit(
|
|
188
|
+
[
|
|
189
|
+
"--no-optional-locks",
|
|
190
|
+
"diff",
|
|
191
|
+
"--no-ext-diff",
|
|
192
|
+
"--no-textconv",
|
|
193
|
+
"HEAD",
|
|
194
|
+
"--shortstat"
|
|
195
|
+
],
|
|
196
|
+
gitRoot
|
|
197
|
+
),
|
|
198
|
+
runGit(
|
|
199
|
+
[
|
|
200
|
+
"--no-optional-locks",
|
|
201
|
+
"ls-files",
|
|
202
|
+
"-z",
|
|
203
|
+
"--others",
|
|
204
|
+
"--exclude-standard"
|
|
205
|
+
],
|
|
206
|
+
gitRoot
|
|
207
|
+
)
|
|
208
|
+
]);
|
|
209
|
+
const untrackedCount = countNulDelimited(untrackedOut);
|
|
210
|
+
const quickStats = shortstatOut != null && parseShortstat(shortstatOut) || EMPTY_STATS;
|
|
211
|
+
if (quickStats.filesCount + untrackedCount > MAX_FILES_FOR_DETAILS) {
|
|
212
|
+
return {
|
|
213
|
+
stats: {
|
|
214
|
+
...quickStats,
|
|
215
|
+
filesCount: quickStats.filesCount + untrackedCount
|
|
216
|
+
},
|
|
217
|
+
perFileStats: /* @__PURE__ */ new Map()
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
const [numstatOut, nameStatusOut] = await Promise.all([
|
|
221
|
+
runGit(
|
|
222
|
+
[
|
|
223
|
+
"--no-optional-locks",
|
|
224
|
+
"diff",
|
|
225
|
+
"--no-ext-diff",
|
|
226
|
+
"--no-textconv",
|
|
227
|
+
"HEAD",
|
|
228
|
+
"--numstat",
|
|
229
|
+
"-z"
|
|
230
|
+
],
|
|
231
|
+
gitRoot
|
|
232
|
+
),
|
|
233
|
+
runGit(
|
|
234
|
+
[
|
|
235
|
+
"--no-optional-locks",
|
|
236
|
+
"diff",
|
|
237
|
+
"--no-ext-diff",
|
|
238
|
+
"--no-textconv",
|
|
239
|
+
"HEAD",
|
|
240
|
+
"--name-status",
|
|
241
|
+
"-z"
|
|
242
|
+
],
|
|
243
|
+
gitRoot
|
|
244
|
+
)
|
|
245
|
+
]);
|
|
246
|
+
if (numstatOut == null) return null;
|
|
247
|
+
const { stats, perFileStats } = parseGitNumstat(numstatOut);
|
|
248
|
+
const deletedPaths = nameStatusOut != null ? parseDeletedFromNameStatus(nameStatusOut) : null;
|
|
249
|
+
if (deletedPaths && deletedPaths.size > 0) {
|
|
250
|
+
for (const [filename, s] of perFileStats) {
|
|
251
|
+
if (deletedPaths.has(filename)) s.isDeleted = true;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
if (untrackedCount > 0) {
|
|
255
|
+
stats.filesCount += untrackedCount;
|
|
256
|
+
const untrackedPaths = splitNulDelimited(untrackedOut);
|
|
257
|
+
const lineStats = await mapWithConcurrency(
|
|
258
|
+
untrackedPaths,
|
|
259
|
+
MAX_FILES,
|
|
260
|
+
(relPath) => countUntrackedLines(path2.join(gitRoot, relPath))
|
|
261
|
+
);
|
|
262
|
+
for (const s of lineStats) stats.linesAdded += s.added;
|
|
263
|
+
const remainingSlots = Math.max(0, MAX_FILES - perFileStats.size);
|
|
264
|
+
const visibleCount = Math.min(remainingSlots, untrackedPaths.length);
|
|
265
|
+
for (let i = 0; i < visibleCount; i++) {
|
|
266
|
+
const relPath = untrackedPaths[i] ?? "";
|
|
267
|
+
const u = lineStats[i] ?? {
|
|
268
|
+
added: 0,
|
|
269
|
+
isBinary: false,
|
|
270
|
+
truncated: false
|
|
271
|
+
};
|
|
272
|
+
perFileStats.set(relPath, {
|
|
273
|
+
added: u.added,
|
|
274
|
+
removed: 0,
|
|
275
|
+
isBinary: u.isBinary,
|
|
276
|
+
isUntracked: true,
|
|
277
|
+
truncated: u.truncated
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
return { stats, perFileStats };
|
|
282
|
+
}
|
|
283
|
+
__name(fetchGitDiff, "fetchGitDiff");
|
|
284
|
+
async function fetchGitDiffHunks(cwd) {
|
|
285
|
+
const gitRoot = findGitRoot(cwd);
|
|
286
|
+
if (!gitRoot) return /* @__PURE__ */ new Map();
|
|
287
|
+
if (await isInTransientGitState(gitRoot)) return /* @__PURE__ */ new Map();
|
|
288
|
+
const diffOut = await runGit(
|
|
289
|
+
["--no-optional-locks", "diff", "--no-ext-diff", "--no-textconv", "HEAD"],
|
|
290
|
+
gitRoot
|
|
291
|
+
);
|
|
292
|
+
if (diffOut == null) return /* @__PURE__ */ new Map();
|
|
293
|
+
return parseGitDiff(diffOut);
|
|
294
|
+
}
|
|
295
|
+
__name(fetchGitDiffHunks, "fetchGitDiffHunks");
|
|
296
|
+
async function fetchGitDiffHunksForFile(cwd, filePath, oldPath) {
|
|
297
|
+
const gitRoot = findGitRoot(cwd);
|
|
298
|
+
if (!gitRoot) return null;
|
|
299
|
+
const relPath = toRepoRelativePath(gitRoot, filePath);
|
|
300
|
+
if (relPath === null) return null;
|
|
301
|
+
if (await isInTransientGitState(gitRoot)) return null;
|
|
302
|
+
const oldRelPath = oldPath != null ? toRepoRelativePath(gitRoot, oldPath) : null;
|
|
303
|
+
const diffArgs = [
|
|
304
|
+
"--no-optional-locks",
|
|
305
|
+
"diff",
|
|
306
|
+
"--no-ext-diff",
|
|
307
|
+
"--no-textconv"
|
|
308
|
+
];
|
|
309
|
+
if (oldRelPath != null) diffArgs.push("-M");
|
|
310
|
+
diffArgs.push("HEAD", "--");
|
|
311
|
+
if (oldRelPath != null) diffArgs.push(oldRelPath);
|
|
312
|
+
diffArgs.push(relPath);
|
|
313
|
+
const diffOut = await runGit(diffArgs, gitRoot);
|
|
314
|
+
if (diffOut == null) return null;
|
|
315
|
+
const truncatedPaths = /* @__PURE__ */ new Set();
|
|
316
|
+
const parsed = parseGitDiff(diffOut, truncatedPaths);
|
|
317
|
+
if (parsed.size > 0) {
|
|
318
|
+
const [key, hunks] = parsed.entries().next().value;
|
|
319
|
+
return { hunks: hunks ?? [], truncated: truncatedPaths.has(key) };
|
|
320
|
+
}
|
|
321
|
+
const untrackedOut = await runGit(
|
|
322
|
+
[
|
|
323
|
+
"--no-optional-locks",
|
|
324
|
+
"ls-files",
|
|
325
|
+
"--others",
|
|
326
|
+
"--exclude-standard",
|
|
327
|
+
"--",
|
|
328
|
+
relPath
|
|
329
|
+
],
|
|
330
|
+
gitRoot
|
|
331
|
+
);
|
|
332
|
+
if (untrackedOut && untrackedOut.trim().length > 0) {
|
|
333
|
+
return synthesizeUntrackedHunk(gitRoot, relPath);
|
|
334
|
+
}
|
|
335
|
+
return null;
|
|
336
|
+
}
|
|
337
|
+
__name(fetchGitDiffHunksForFile, "fetchGitDiffHunksForFile");
|
|
338
|
+
function toRepoRelativePath(gitRoot, filePath) {
|
|
339
|
+
if (!path2.isAbsolute(filePath)) {
|
|
340
|
+
if (filePath.length === 0) return null;
|
|
341
|
+
if (filePath.startsWith("/") || filePath.startsWith("\\")) return null;
|
|
342
|
+
if (/^[A-Za-z]:/.test(filePath)) return null;
|
|
343
|
+
if (filePath.split(/[\\/]/).some((segment) => segment === ".."))
|
|
344
|
+
return null;
|
|
345
|
+
return filePath;
|
|
346
|
+
}
|
|
347
|
+
const rel = path2.relative(gitRoot, filePath);
|
|
348
|
+
if (rel === "" || rel === ".." || rel.startsWith(`..${path2.sep}`) || path2.isAbsolute(rel))
|
|
349
|
+
return null;
|
|
350
|
+
return rel;
|
|
351
|
+
}
|
|
352
|
+
__name(toRepoRelativePath, "toRepoRelativePath");
|
|
353
|
+
async function synthesizeUntrackedHunk(gitRoot, filePath) {
|
|
354
|
+
const absPath = path2.join(gitRoot, filePath);
|
|
355
|
+
try {
|
|
356
|
+
const lst = await lstat(absPath);
|
|
357
|
+
if (!lst.isFile()) return null;
|
|
358
|
+
} catch {
|
|
359
|
+
return null;
|
|
360
|
+
}
|
|
361
|
+
const fh = await openUntrackedForDiffRead(absPath);
|
|
362
|
+
if (!fh) return null;
|
|
363
|
+
try {
|
|
364
|
+
const st = await fh.stat();
|
|
365
|
+
if (!st.isFile()) return null;
|
|
366
|
+
const cap = Math.min(st.size, MAX_DIFF_SIZE_BYTES);
|
|
367
|
+
const buf = Buffer.allocUnsafe(cap);
|
|
368
|
+
let offset = 0;
|
|
369
|
+
while (offset < cap) {
|
|
370
|
+
const { bytesRead } = await fh.read(buf, offset, cap - offset, offset);
|
|
371
|
+
if (bytesRead === 0) break;
|
|
372
|
+
offset += bytesRead;
|
|
373
|
+
}
|
|
374
|
+
const sniffEnd = Math.min(offset, BINARY_SNIFF_BYTES);
|
|
375
|
+
for (let i = 0; i < sniffEnd; i++) {
|
|
376
|
+
if (buf[i] === 0) return null;
|
|
377
|
+
}
|
|
378
|
+
const lines = buf.toString("utf8", 0, offset).split("\n");
|
|
379
|
+
if (lines.length > 0 && lines[lines.length - 1] === "") lines.pop();
|
|
380
|
+
const capped = lines.slice(0, MAX_LINES_PER_FILE);
|
|
381
|
+
const truncated = st.size > MAX_DIFF_SIZE_BYTES || lines.length > capped.length;
|
|
382
|
+
if (capped.length === 0) return { hunks: [], truncated };
|
|
383
|
+
return {
|
|
384
|
+
hunks: [
|
|
385
|
+
{
|
|
386
|
+
oldStart: 0,
|
|
387
|
+
oldLines: 0,
|
|
388
|
+
newStart: 1,
|
|
389
|
+
newLines: capped.length,
|
|
390
|
+
lines: capped.map((line) => "+" + line)
|
|
391
|
+
}
|
|
392
|
+
],
|
|
393
|
+
truncated
|
|
394
|
+
};
|
|
395
|
+
} catch {
|
|
396
|
+
return null;
|
|
397
|
+
} finally {
|
|
398
|
+
await fh.close().catch(() => {
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
__name(synthesizeUntrackedHunk, "synthesizeUntrackedHunk");
|
|
403
|
+
function forEachNumstatEntry(stdout, visit) {
|
|
404
|
+
const tokens = stdout.split("\0");
|
|
405
|
+
if (tokens.length > 0 && tokens[tokens.length - 1] === "") tokens.pop();
|
|
406
|
+
let pending = null;
|
|
407
|
+
let renameOld = null;
|
|
408
|
+
for (const token of tokens) {
|
|
409
|
+
if (pending) {
|
|
410
|
+
if (renameOld === null) {
|
|
411
|
+
renameOld = token;
|
|
412
|
+
continue;
|
|
413
|
+
}
|
|
414
|
+
visit({ ...pending, path: token, oldPath: renameOld });
|
|
415
|
+
pending = null;
|
|
416
|
+
renameOld = null;
|
|
417
|
+
continue;
|
|
418
|
+
}
|
|
419
|
+
const firstTab = token.indexOf(" ");
|
|
420
|
+
if (firstTab < 0) continue;
|
|
421
|
+
const secondTab = token.indexOf(" ", firstTab + 1);
|
|
422
|
+
if (secondTab < 0) continue;
|
|
423
|
+
const addStr = token.slice(0, firstTab);
|
|
424
|
+
const remStr = token.slice(firstTab + 1, secondTab);
|
|
425
|
+
const path6 = token.slice(secondTab + 1);
|
|
426
|
+
const isBinary = addStr === "-" || remStr === "-";
|
|
427
|
+
const added = isBinary ? 0 : parseInt(addStr, 10) || 0;
|
|
428
|
+
const removed = isBinary ? 0 : parseInt(remStr, 10) || 0;
|
|
429
|
+
if (path6 === "") {
|
|
430
|
+
pending = { added, removed, isBinary };
|
|
431
|
+
continue;
|
|
432
|
+
}
|
|
433
|
+
visit({ path: path6, added, removed, isBinary });
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
__name(forEachNumstatEntry, "forEachNumstatEntry");
|
|
437
|
+
function parseGitNumstat(stdout) {
|
|
438
|
+
let added = 0;
|
|
439
|
+
let removed = 0;
|
|
440
|
+
let validFileCount = 0;
|
|
441
|
+
const perFileStats = /* @__PURE__ */ new Map();
|
|
442
|
+
forEachNumstatEntry(stdout, (entry) => {
|
|
443
|
+
commitEntry(
|
|
444
|
+
entry.path,
|
|
445
|
+
entry.added,
|
|
446
|
+
entry.removed,
|
|
447
|
+
entry.isBinary,
|
|
448
|
+
entry.oldPath
|
|
449
|
+
);
|
|
450
|
+
});
|
|
451
|
+
function commitEntry(filePath, fileAdded, fileRemoved, isBinary, oldPath) {
|
|
452
|
+
validFileCount++;
|
|
453
|
+
added += fileAdded;
|
|
454
|
+
removed += fileRemoved;
|
|
455
|
+
if (perFileStats.size < MAX_FILES) {
|
|
456
|
+
perFileStats.set(filePath, {
|
|
457
|
+
added: fileAdded,
|
|
458
|
+
removed: fileRemoved,
|
|
459
|
+
isBinary,
|
|
460
|
+
...oldPath ? { oldPath } : {}
|
|
461
|
+
});
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
__name(commitEntry, "commitEntry");
|
|
465
|
+
return {
|
|
466
|
+
stats: {
|
|
467
|
+
filesCount: validFileCount,
|
|
468
|
+
linesAdded: added,
|
|
469
|
+
linesRemoved: removed
|
|
470
|
+
},
|
|
471
|
+
perFileStats
|
|
472
|
+
};
|
|
473
|
+
}
|
|
474
|
+
__name(parseGitNumstat, "parseGitNumstat");
|
|
475
|
+
function parseGitDiff(stdout, truncatedPaths) {
|
|
476
|
+
const result = /* @__PURE__ */ new Map();
|
|
477
|
+
if (!stdout.trim()) return result;
|
|
478
|
+
const fileDiffs = stdout.split(/^diff --git /m).filter(Boolean);
|
|
479
|
+
for (const fileDiff of fileDiffs) {
|
|
480
|
+
if (result.size >= MAX_FILES) break;
|
|
481
|
+
if (Buffer.byteLength(fileDiff, "utf8") > MAX_DIFF_SIZE_BYTES) continue;
|
|
482
|
+
const lines = fileDiff.split("\n");
|
|
483
|
+
const filePath = extractFilePath(lines);
|
|
484
|
+
if (filePath === null) continue;
|
|
485
|
+
const fileHunks = [];
|
|
486
|
+
let currentHunk = null;
|
|
487
|
+
let lineCount = 0;
|
|
488
|
+
for (let i = 1; i < lines.length; i++) {
|
|
489
|
+
const line = lines[i] ?? "";
|
|
490
|
+
const hunkMatch = line.match(
|
|
491
|
+
/^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/
|
|
492
|
+
);
|
|
493
|
+
if (hunkMatch) {
|
|
494
|
+
if (currentHunk) fileHunks.push(currentHunk);
|
|
495
|
+
currentHunk = {
|
|
496
|
+
oldStart: parseInt(hunkMatch[1] ?? "0", 10),
|
|
497
|
+
oldLines: parseInt(hunkMatch[2] ?? "1", 10),
|
|
498
|
+
newStart: parseInt(hunkMatch[3] ?? "0", 10),
|
|
499
|
+
newLines: parseInt(hunkMatch[4] ?? "1", 10),
|
|
500
|
+
lines: []
|
|
501
|
+
};
|
|
502
|
+
continue;
|
|
503
|
+
}
|
|
504
|
+
if (!currentHunk) {
|
|
505
|
+
continue;
|
|
506
|
+
}
|
|
507
|
+
if (line.startsWith("+") || line.startsWith("-") || line.startsWith(" ")) {
|
|
508
|
+
if (lineCount >= MAX_LINES_PER_FILE) {
|
|
509
|
+
truncatedPaths?.add(filePath);
|
|
510
|
+
break;
|
|
511
|
+
}
|
|
512
|
+
currentHunk.lines.push("" + line);
|
|
513
|
+
lineCount++;
|
|
514
|
+
} else if (line.startsWith("\\")) {
|
|
515
|
+
currentHunk.lines.push("" + line);
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
if (currentHunk) fileHunks.push(currentHunk);
|
|
519
|
+
if (fileHunks.length > 0) result.set(filePath, fileHunks);
|
|
520
|
+
}
|
|
521
|
+
return result;
|
|
522
|
+
}
|
|
523
|
+
__name(parseGitDiff, "parseGitDiff");
|
|
524
|
+
function unquoteCStylePath(s) {
|
|
525
|
+
if (!s.startsWith('"') || !s.endsWith('"') || s.length < 2) return s;
|
|
526
|
+
const inner = s.slice(1, -1);
|
|
527
|
+
const bytes = [];
|
|
528
|
+
let i = 0;
|
|
529
|
+
while (i < inner.length) {
|
|
530
|
+
const c = inner.charCodeAt(i);
|
|
531
|
+
if (c !== 92) {
|
|
532
|
+
const cp = inner.codePointAt(i);
|
|
533
|
+
if (cp === void 0) {
|
|
534
|
+
i++;
|
|
535
|
+
continue;
|
|
536
|
+
}
|
|
537
|
+
const ch = String.fromCodePoint(cp);
|
|
538
|
+
bytes.push(...Buffer.from(ch, "utf8"));
|
|
539
|
+
i += ch.length;
|
|
540
|
+
continue;
|
|
541
|
+
}
|
|
542
|
+
const next = inner[i + 1];
|
|
543
|
+
if (next === void 0) {
|
|
544
|
+
bytes.push(92);
|
|
545
|
+
i++;
|
|
546
|
+
continue;
|
|
547
|
+
}
|
|
548
|
+
switch (next) {
|
|
549
|
+
case "a":
|
|
550
|
+
bytes.push(7);
|
|
551
|
+
i += 2;
|
|
552
|
+
break;
|
|
553
|
+
case "b":
|
|
554
|
+
bytes.push(8);
|
|
555
|
+
i += 2;
|
|
556
|
+
break;
|
|
557
|
+
case "f":
|
|
558
|
+
bytes.push(12);
|
|
559
|
+
i += 2;
|
|
560
|
+
break;
|
|
561
|
+
case "v":
|
|
562
|
+
bytes.push(11);
|
|
563
|
+
i += 2;
|
|
564
|
+
break;
|
|
565
|
+
case "t":
|
|
566
|
+
bytes.push(9);
|
|
567
|
+
i += 2;
|
|
568
|
+
break;
|
|
569
|
+
case "n":
|
|
570
|
+
bytes.push(10);
|
|
571
|
+
i += 2;
|
|
572
|
+
break;
|
|
573
|
+
case "r":
|
|
574
|
+
bytes.push(13);
|
|
575
|
+
i += 2;
|
|
576
|
+
break;
|
|
577
|
+
case '"':
|
|
578
|
+
bytes.push(34);
|
|
579
|
+
i += 2;
|
|
580
|
+
break;
|
|
581
|
+
case "\\":
|
|
582
|
+
bytes.push(92);
|
|
583
|
+
i += 2;
|
|
584
|
+
break;
|
|
585
|
+
default:
|
|
586
|
+
if (next >= "0" && next <= "7") {
|
|
587
|
+
let octal = "";
|
|
588
|
+
while (octal.length < 3 && i + 1 + octal.length < inner.length && (inner[i + 1 + octal.length] ?? "") >= "0" && (inner[i + 1 + octal.length] ?? "") <= "7") {
|
|
589
|
+
octal += inner[i + 1 + octal.length];
|
|
590
|
+
}
|
|
591
|
+
bytes.push(parseInt(octal, 8) & 255);
|
|
592
|
+
i += 1 + octal.length;
|
|
593
|
+
} else {
|
|
594
|
+
bytes.push(...Buffer.from(next, "utf8"));
|
|
595
|
+
i += 2;
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
return Buffer.from(bytes).toString("utf8");
|
|
600
|
+
}
|
|
601
|
+
__name(unquoteCStylePath, "unquoteCStylePath");
|
|
602
|
+
function extractFilePath(lines) {
|
|
603
|
+
let plus = null;
|
|
604
|
+
let minus = null;
|
|
605
|
+
let renameTo = null;
|
|
606
|
+
let copyTo = null;
|
|
607
|
+
for (const line of lines) {
|
|
608
|
+
if (line.startsWith("@@ ")) break;
|
|
609
|
+
if (line.startsWith("+++ ")) plus = line.slice(4);
|
|
610
|
+
else if (line.startsWith("--- ")) minus = line.slice(4);
|
|
611
|
+
else if (line.startsWith("rename to ")) renameTo = line.slice(10);
|
|
612
|
+
else if (line.startsWith("copy to ")) copyTo = line.slice(8);
|
|
613
|
+
}
|
|
614
|
+
const stripTab = /* @__PURE__ */ __name((s) => {
|
|
615
|
+
const t = s.indexOf(" ");
|
|
616
|
+
return t >= 0 ? s.slice(0, t) : s;
|
|
617
|
+
}, "stripTab");
|
|
618
|
+
const normalize = /* @__PURE__ */ __name((s) => unquoteCStylePath(stripTab(s)), "normalize");
|
|
619
|
+
if (renameTo !== null) return normalize(renameTo);
|
|
620
|
+
if (copyTo !== null) return normalize(copyTo);
|
|
621
|
+
if (plus !== null) {
|
|
622
|
+
const p = normalize(plus);
|
|
623
|
+
if (p !== "/dev/null" && p.startsWith("b/")) return p.slice(2);
|
|
624
|
+
if (minus !== null) {
|
|
625
|
+
const m = normalize(minus);
|
|
626
|
+
if (m !== "/dev/null" && m.startsWith("a/")) return m.slice(2);
|
|
627
|
+
}
|
|
628
|
+
return null;
|
|
629
|
+
}
|
|
630
|
+
if (minus !== null) {
|
|
631
|
+
const m = normalize(minus);
|
|
632
|
+
if (m !== "/dev/null" && m.startsWith("a/")) return m.slice(2);
|
|
633
|
+
}
|
|
634
|
+
return null;
|
|
635
|
+
}
|
|
636
|
+
__name(extractFilePath, "extractFilePath");
|
|
637
|
+
function parseShortstat(stdout) {
|
|
638
|
+
const match = stdout.match(
|
|
639
|
+
/^ ?(\d{1,10}) files? changed(?:, (\d{1,10}) insertions?\(\+\))?(?:, (\d{1,10}) deletions?\(-\))?$/m
|
|
640
|
+
);
|
|
641
|
+
if (!match) return null;
|
|
642
|
+
return {
|
|
643
|
+
filesCount: parseInt(match[1] ?? "0", 10),
|
|
644
|
+
linesAdded: parseInt(match[2] ?? "0", 10),
|
|
645
|
+
linesRemoved: parseInt(match[3] ?? "0", 10)
|
|
646
|
+
};
|
|
647
|
+
}
|
|
648
|
+
__name(parseShortstat, "parseShortstat");
|
|
649
|
+
async function getWorkingTreeLineStats(cwd) {
|
|
650
|
+
const stdout = await runGit(
|
|
651
|
+
["diff-index", "--no-ext-diff", "--no-textconv", "--shortstat", "HEAD"],
|
|
652
|
+
cwd
|
|
653
|
+
);
|
|
654
|
+
if (stdout === null) return null;
|
|
655
|
+
const stats = parseShortstat(stdout);
|
|
656
|
+
return {
|
|
657
|
+
linesAdded: stats?.linesAdded ?? 0,
|
|
658
|
+
linesRemoved: stats?.linesRemoved ?? 0
|
|
659
|
+
};
|
|
660
|
+
}
|
|
661
|
+
__name(getWorkingTreeLineStats, "getWorkingTreeLineStats");
|
|
662
|
+
function parseDeletedFromNameStatus(stdout) {
|
|
663
|
+
const tokens = stdout.split("\0");
|
|
664
|
+
if (tokens.length > 0 && tokens[tokens.length - 1] === "") tokens.pop();
|
|
665
|
+
const deleted = /* @__PURE__ */ new Set();
|
|
666
|
+
let i = 0;
|
|
667
|
+
while (i < tokens.length) {
|
|
668
|
+
const status = tokens[i] ?? "";
|
|
669
|
+
i++;
|
|
670
|
+
if (status === "") continue;
|
|
671
|
+
const head = status[0];
|
|
672
|
+
if (head === "R" || head === "C") {
|
|
673
|
+
i += 2;
|
|
674
|
+
continue;
|
|
675
|
+
}
|
|
676
|
+
const path6 = tokens[i] ?? "";
|
|
677
|
+
i++;
|
|
678
|
+
if (head === "D" && path6 !== "") deleted.add(path6);
|
|
679
|
+
}
|
|
680
|
+
return deleted;
|
|
681
|
+
}
|
|
682
|
+
__name(parseDeletedFromNameStatus, "parseDeletedFromNameStatus");
|
|
683
|
+
function countNulDelimited(stdout) {
|
|
684
|
+
if (!stdout) return 0;
|
|
685
|
+
let count = 0;
|
|
686
|
+
for (let i = 0; i < stdout.length; i++) {
|
|
687
|
+
if (stdout.charCodeAt(i) === 0) count++;
|
|
688
|
+
}
|
|
689
|
+
return count;
|
|
690
|
+
}
|
|
691
|
+
__name(countNulDelimited, "countNulDelimited");
|
|
692
|
+
function splitNulDelimited(stdout) {
|
|
693
|
+
if (!stdout) return [];
|
|
694
|
+
return stdout.split("\0").filter(Boolean);
|
|
695
|
+
}
|
|
696
|
+
__name(splitNulDelimited, "splitNulDelimited");
|
|
697
|
+
async function countUntrackedLines(absPath) {
|
|
698
|
+
let st;
|
|
699
|
+
try {
|
|
700
|
+
st = await lstat(absPath);
|
|
701
|
+
} catch {
|
|
702
|
+
return { added: 0, isBinary: true, truncated: false };
|
|
703
|
+
}
|
|
704
|
+
if (!st.isFile()) {
|
|
705
|
+
return { added: 0, isBinary: true, truncated: false };
|
|
706
|
+
}
|
|
707
|
+
const fh = await openUntrackedForDiffRead(absPath);
|
|
708
|
+
if (!fh) {
|
|
709
|
+
return { added: 0, isBinary: true, truncated: false };
|
|
710
|
+
}
|
|
711
|
+
try {
|
|
712
|
+
const buf = Buffer.allocUnsafe(UNTRACKED_READ_CHUNK_BYTES);
|
|
713
|
+
let totalRead = 0;
|
|
714
|
+
let lines = 0;
|
|
715
|
+
let lastByte = -1;
|
|
716
|
+
let sniffedBytes = 0;
|
|
717
|
+
while (totalRead < UNTRACKED_READ_CAP_BYTES) {
|
|
718
|
+
const remaining = UNTRACKED_READ_CAP_BYTES - totalRead;
|
|
719
|
+
const toRead = Math.min(buf.length, remaining);
|
|
720
|
+
const { bytesRead } = await fh.read(buf, 0, toRead, totalRead);
|
|
721
|
+
if (bytesRead === 0) break;
|
|
722
|
+
if (sniffedBytes < BINARY_SNIFF_BYTES) {
|
|
723
|
+
const sniffEnd = Math.min(bytesRead, BINARY_SNIFF_BYTES - sniffedBytes);
|
|
724
|
+
for (let i = 0; i < sniffEnd; i++) {
|
|
725
|
+
if (buf[i] === 0) {
|
|
726
|
+
return { added: 0, isBinary: true, truncated: false };
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
sniffedBytes += sniffEnd;
|
|
730
|
+
}
|
|
731
|
+
for (let i = 0; i < bytesRead; i++) {
|
|
732
|
+
if (buf[i] === 10) lines++;
|
|
733
|
+
}
|
|
734
|
+
lastByte = buf[bytesRead - 1] ?? -1;
|
|
735
|
+
totalRead += bytesRead;
|
|
736
|
+
}
|
|
737
|
+
if (totalRead === 0) {
|
|
738
|
+
return { added: 0, isBinary: false, truncated: false };
|
|
739
|
+
}
|
|
740
|
+
let truncated = false;
|
|
741
|
+
if (totalRead >= UNTRACKED_READ_CAP_BYTES) {
|
|
742
|
+
const { size } = await fh.stat();
|
|
743
|
+
truncated = size > totalRead;
|
|
744
|
+
}
|
|
745
|
+
if (!truncated && lastByte !== 10) lines++;
|
|
746
|
+
return { added: lines, isBinary: false, truncated };
|
|
747
|
+
} catch {
|
|
748
|
+
return { added: 0, isBinary: true, truncated: false };
|
|
749
|
+
} finally {
|
|
750
|
+
await fh.close().catch(() => {
|
|
751
|
+
});
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
__name(countUntrackedLines, "countUntrackedLines");
|
|
755
|
+
async function resolveGitDir(cwd) {
|
|
756
|
+
const gitRoot = findGitRoot(cwd);
|
|
757
|
+
if (!gitRoot) return null;
|
|
758
|
+
return resolveGitDirFromRoot(gitRoot);
|
|
759
|
+
}
|
|
760
|
+
__name(resolveGitDir, "resolveGitDir");
|
|
761
|
+
async function resolveGitDirFromRoot(gitRoot) {
|
|
762
|
+
const dotGit = path2.join(gitRoot, ".git");
|
|
763
|
+
try {
|
|
764
|
+
const s = await stat(dotGit);
|
|
765
|
+
if (s.isDirectory()) return dotGit;
|
|
766
|
+
if (!s.isFile()) return null;
|
|
767
|
+
const content = await readFile2(dotGit, "utf8");
|
|
768
|
+
const match = content.match(/^gitdir:\s*(.+?)\s*$/m);
|
|
769
|
+
if (!match || !match[1]) return null;
|
|
770
|
+
const raw = match[1];
|
|
771
|
+
return path2.isAbsolute(raw) ? raw : path2.resolve(gitRoot, raw);
|
|
772
|
+
} catch {
|
|
773
|
+
return null;
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
__name(resolveGitDirFromRoot, "resolveGitDirFromRoot");
|
|
777
|
+
async function isInTransientGitState(gitRoot) {
|
|
778
|
+
const gitDir = await resolveGitDirFromRoot(gitRoot);
|
|
779
|
+
if (!gitDir) return false;
|
|
780
|
+
const transientPaths = [
|
|
781
|
+
"MERGE_HEAD",
|
|
782
|
+
"CHERRY_PICK_HEAD",
|
|
783
|
+
"REVERT_HEAD",
|
|
784
|
+
"rebase-merge",
|
|
785
|
+
"rebase-apply"
|
|
786
|
+
];
|
|
787
|
+
const results = await Promise.all(
|
|
788
|
+
transientPaths.map(
|
|
789
|
+
(name) => access(path2.join(gitDir, name)).then(() => true).catch(() => false)
|
|
790
|
+
)
|
|
791
|
+
);
|
|
792
|
+
return results.some(Boolean);
|
|
793
|
+
}
|
|
794
|
+
__name(isInTransientGitState, "isInTransientGitState");
|
|
795
|
+
async function mapWithConcurrency(items, limit, fn) {
|
|
796
|
+
if (items.length === 0) return [];
|
|
797
|
+
const effective = Math.max(1, limit);
|
|
798
|
+
const results = new Array(items.length);
|
|
799
|
+
for (let i = 0; i < items.length; i += effective) {
|
|
800
|
+
const slice = items.slice(i, i + effective);
|
|
801
|
+
const batch = await Promise.all(slice.map((item) => fn(item)));
|
|
802
|
+
for (let j = 0; j < batch.length; j++) {
|
|
803
|
+
results[i + j] = batch[j];
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
return results;
|
|
807
|
+
}
|
|
808
|
+
__name(mapWithConcurrency, "mapWithConcurrency");
|
|
809
|
+
async function runGit(args, cwd) {
|
|
810
|
+
const fullArgs = ["-c", "core.quotepath=false", ...NO_EXEC_CONFIG, ...args];
|
|
811
|
+
try {
|
|
812
|
+
const { stdout } = await execFileAsync("git", fullArgs, {
|
|
813
|
+
cwd,
|
|
814
|
+
timeout: GIT_TIMEOUT_MS,
|
|
815
|
+
maxBuffer: 64 * 1024 * 1024,
|
|
816
|
+
windowsHide: true,
|
|
817
|
+
encoding: "utf8"
|
|
818
|
+
});
|
|
819
|
+
return stdout;
|
|
820
|
+
} catch {
|
|
821
|
+
return null;
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
__name(runGit, "runGit");
|
|
825
|
+
async function getGitWorkingTreeStatus(cwd) {
|
|
826
|
+
const gitRoot = findGitRoot(cwd);
|
|
827
|
+
if (!gitRoot) return null;
|
|
828
|
+
const stdout = await runGit(
|
|
829
|
+
["--no-optional-locks", "status", "--porcelain=v1", "--branch", "-z"],
|
|
830
|
+
gitRoot
|
|
831
|
+
);
|
|
832
|
+
if (stdout == null) return null;
|
|
833
|
+
const tokens = stdout.split("\0");
|
|
834
|
+
if (tokens.length > 0 && tokens[tokens.length - 1] === "") tokens.pop();
|
|
835
|
+
const head = tokens.shift() ?? "";
|
|
836
|
+
const branch = parseStatusBranchLine(head);
|
|
837
|
+
const counts = parseStatusEntries(tokens);
|
|
838
|
+
const [stashCount, operation] = await Promise.all([
|
|
839
|
+
countStashEntries(gitRoot),
|
|
840
|
+
detectGitOperation(gitRoot)
|
|
841
|
+
]);
|
|
842
|
+
return {
|
|
843
|
+
...branch,
|
|
844
|
+
...counts,
|
|
845
|
+
stashCount,
|
|
846
|
+
...operation ? { operation } : {}
|
|
847
|
+
};
|
|
848
|
+
}
|
|
849
|
+
__name(getGitWorkingTreeStatus, "getGitWorkingTreeStatus");
|
|
850
|
+
var NO_BRANCH = {
|
|
851
|
+
branch: null,
|
|
852
|
+
detached: false,
|
|
853
|
+
hasUpstream: false,
|
|
854
|
+
ahead: 0,
|
|
855
|
+
behind: 0
|
|
856
|
+
};
|
|
857
|
+
function parseStatusBranchLine(line) {
|
|
858
|
+
if (!line.startsWith("## ")) return { ...NO_BRANCH };
|
|
859
|
+
let desc = line.slice(3);
|
|
860
|
+
if (desc.startsWith("HEAD (no branch)")) {
|
|
861
|
+
return { ...NO_BRANCH, detached: true };
|
|
862
|
+
}
|
|
863
|
+
const unborn = desc.match(/^(?:No commits yet|Initial commit) on (.+)$/);
|
|
864
|
+
if (unborn) {
|
|
865
|
+
return { ...NO_BRANCH, branch: unborn[1] ?? null };
|
|
866
|
+
}
|
|
867
|
+
let ahead = 0;
|
|
868
|
+
let behind = 0;
|
|
869
|
+
const bracket = desc.match(/ \[([^\]]*)\]$/);
|
|
870
|
+
if (bracket) {
|
|
871
|
+
const inner = bracket[1] ?? "";
|
|
872
|
+
const aheadMatch = inner.match(/ahead (\d+)/);
|
|
873
|
+
const behindMatch = inner.match(/behind (\d+)/);
|
|
874
|
+
ahead = aheadMatch ? Number(aheadMatch[1]) : 0;
|
|
875
|
+
behind = behindMatch ? Number(behindMatch[1]) : 0;
|
|
876
|
+
desc = desc.slice(0, bracket.index).trimEnd();
|
|
877
|
+
}
|
|
878
|
+
const hasUpstream = desc.includes("...");
|
|
879
|
+
const sepIndex = desc.lastIndexOf("...");
|
|
880
|
+
const branch = sepIndex >= 0 ? desc.slice(0, sepIndex) : desc;
|
|
881
|
+
return {
|
|
882
|
+
branch: branch || null,
|
|
883
|
+
detached: false,
|
|
884
|
+
hasUpstream,
|
|
885
|
+
ahead,
|
|
886
|
+
behind
|
|
887
|
+
};
|
|
888
|
+
}
|
|
889
|
+
__name(parseStatusBranchLine, "parseStatusBranchLine");
|
|
890
|
+
function isUnmergedEntry(x, y) {
|
|
891
|
+
if (x === "U" || y === "U") return true;
|
|
892
|
+
if (x === "D" && y === "D") return true;
|
|
893
|
+
if (x === "A" && y === "A") return true;
|
|
894
|
+
return false;
|
|
895
|
+
}
|
|
896
|
+
__name(isUnmergedEntry, "isUnmergedEntry");
|
|
897
|
+
function parseStatusEntries(tokens) {
|
|
898
|
+
let staged = 0;
|
|
899
|
+
let unstaged = 0;
|
|
900
|
+
let untracked = 0;
|
|
901
|
+
let conflicted = 0;
|
|
902
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
903
|
+
const tok = tokens[i] ?? "";
|
|
904
|
+
if (tok.length < 3 || tok[2] !== " ") continue;
|
|
905
|
+
const x = tok[0];
|
|
906
|
+
const y = tok[1];
|
|
907
|
+
if (x === "?" && y === "?") {
|
|
908
|
+
untracked++;
|
|
909
|
+
continue;
|
|
910
|
+
}
|
|
911
|
+
if (x === "!" && y === "!") continue;
|
|
912
|
+
if (isUnmergedEntry(x, y)) {
|
|
913
|
+
conflicted++;
|
|
914
|
+
continue;
|
|
915
|
+
}
|
|
916
|
+
if (x !== " " && x !== "?") staged++;
|
|
917
|
+
if (y !== " " && y !== "?") unstaged++;
|
|
918
|
+
if (x === "R" || x === "C" || y === "R" || y === "C") i++;
|
|
919
|
+
}
|
|
920
|
+
return { staged, unstaged, untracked, conflicted };
|
|
921
|
+
}
|
|
922
|
+
__name(parseStatusEntries, "parseStatusEntries");
|
|
923
|
+
async function detectGitOperation(gitRoot) {
|
|
924
|
+
const gitDir = await resolveGitDirFromRoot(gitRoot);
|
|
925
|
+
if (!gitDir) return void 0;
|
|
926
|
+
const checks = [
|
|
927
|
+
["rebase-merge", "rebase"],
|
|
928
|
+
["rebase-apply", "rebase"],
|
|
929
|
+
["MERGE_HEAD", "merge"],
|
|
930
|
+
["CHERRY_PICK_HEAD", "cherry-pick"],
|
|
931
|
+
["REVERT_HEAD", "revert"],
|
|
932
|
+
["BISECT_LOG", "bisect"]
|
|
933
|
+
];
|
|
934
|
+
for (const [name, op] of checks) {
|
|
935
|
+
try {
|
|
936
|
+
await access(path2.join(gitDir, name));
|
|
937
|
+
return op;
|
|
938
|
+
} catch {
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
return void 0;
|
|
942
|
+
}
|
|
943
|
+
__name(detectGitOperation, "detectGitOperation");
|
|
944
|
+
async function resolveCommonGitDir(gitDir) {
|
|
945
|
+
const raw = (await readFirstLineNoFollow(path2.join(gitDir, "commondir")))?.trim();
|
|
946
|
+
if (!raw) return gitDir;
|
|
947
|
+
return path2.isAbsolute(raw) ? raw : path2.resolve(gitDir, raw);
|
|
948
|
+
}
|
|
949
|
+
__name(resolveCommonGitDir, "resolveCommonGitDir");
|
|
950
|
+
async function countStashEntries(gitRoot) {
|
|
951
|
+
const gitDir = await resolveGitDirFromRoot(gitRoot);
|
|
952
|
+
if (!gitDir) return 0;
|
|
953
|
+
const commonDir = await resolveCommonGitDir(gitDir);
|
|
954
|
+
const stashLog = path2.join(commonDir, "logs", "refs", "stash");
|
|
955
|
+
try {
|
|
956
|
+
const st = await lstat(stashLog);
|
|
957
|
+
if (!st.isFile()) return 0;
|
|
958
|
+
} catch {
|
|
959
|
+
return 0;
|
|
960
|
+
}
|
|
961
|
+
try {
|
|
962
|
+
const content = await readFile2(stashLog, "utf8");
|
|
963
|
+
return content.split("\n").filter((line) => line.trim().length > 0).length;
|
|
964
|
+
} catch {
|
|
965
|
+
return 0;
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
__name(countStashEntries, "countStashEntries");
|
|
969
|
+
var MAX_LOG_LIMIT = 200;
|
|
970
|
+
var DEFAULT_LOG_LIMIT = 50;
|
|
971
|
+
var LOG_FORMAT = "%H%x00%h%x00%an%x00%ae%x00%at%x00%s%x00%D%x00%P%x00%ct";
|
|
972
|
+
var LOG_FIELDS = 9;
|
|
973
|
+
var LOG_DETAIL_FORMAT = "%H%x00%h%x00%an%x00%ae%x00%at%x00%s%x00%D%x00%P%x00%b";
|
|
974
|
+
function parseLogFields(parts) {
|
|
975
|
+
if (parts.length !== LOG_FIELDS) return null;
|
|
976
|
+
return {
|
|
977
|
+
sha: parts[0],
|
|
978
|
+
shortSha: parts[1],
|
|
979
|
+
authorName: parts[2],
|
|
980
|
+
authorEmail: parts[3],
|
|
981
|
+
authorDate: parseInt(parts[4], 10) || 0,
|
|
982
|
+
subject: parts[5],
|
|
983
|
+
refs: parts[6],
|
|
984
|
+
parents: parts[7] ? parts[7].split(" ").filter(Boolean) : [],
|
|
985
|
+
commitDate: parseInt(parts[8], 10) || 0
|
|
986
|
+
};
|
|
987
|
+
}
|
|
988
|
+
__name(parseLogFields, "parseLogFields");
|
|
989
|
+
var LOG_ALL_REVISIONS = ["HEAD", "--branches", "--tags", "--remotes"];
|
|
990
|
+
function isSafeLogRange(range) {
|
|
991
|
+
return !range.startsWith("-") && !range.startsWith("..") && /^[A-Za-z0-9_./~^-]+$/.test(range);
|
|
992
|
+
}
|
|
993
|
+
__name(isSafeLogRange, "isSafeLogRange");
|
|
994
|
+
function parseLogRecords(stdout) {
|
|
995
|
+
const fields = stdout.split("\0");
|
|
996
|
+
if (fields.at(-1) === "") fields.pop();
|
|
997
|
+
const entries = [];
|
|
998
|
+
for (let i = 0; i + LOG_FIELDS <= fields.length; i += LOG_FIELDS) {
|
|
999
|
+
const entry = parseLogFields(fields.slice(i, i + LOG_FIELDS));
|
|
1000
|
+
if (entry) entries.push(entry);
|
|
1001
|
+
}
|
|
1002
|
+
return entries;
|
|
1003
|
+
}
|
|
1004
|
+
__name(parseLogRecords, "parseLogRecords");
|
|
1005
|
+
async function fetchGitLog(cwd, options) {
|
|
1006
|
+
const gitRoot = findGitRoot(cwd);
|
|
1007
|
+
if (!gitRoot) return null;
|
|
1008
|
+
const limit = Math.min(
|
|
1009
|
+
Math.max(options?.limit ?? DEFAULT_LOG_LIMIT, 1),
|
|
1010
|
+
MAX_LOG_LIMIT
|
|
1011
|
+
);
|
|
1012
|
+
const skip = Math.max(options?.skip ?? 0, 0);
|
|
1013
|
+
const revisions = options?.all ? LOG_ALL_REVISIONS : options?.range && isSafeLogRange(options.range) ? [options.range] : [];
|
|
1014
|
+
const search = options?.search?.trim();
|
|
1015
|
+
if (search) {
|
|
1016
|
+
return searchGitLog(gitRoot, revisions, search, limit, skip);
|
|
1017
|
+
}
|
|
1018
|
+
const stdout = await runGit(
|
|
1019
|
+
[
|
|
1020
|
+
"--no-optional-locks",
|
|
1021
|
+
"log",
|
|
1022
|
+
"-z",
|
|
1023
|
+
`--format=${LOG_FORMAT}`,
|
|
1024
|
+
"--date-order",
|
|
1025
|
+
"-n",
|
|
1026
|
+
String(limit + 1),
|
|
1027
|
+
...skip > 0 ? ["--skip", String(skip)] : [],
|
|
1028
|
+
...revisions,
|
|
1029
|
+
"--"
|
|
1030
|
+
],
|
|
1031
|
+
gitRoot
|
|
1032
|
+
);
|
|
1033
|
+
if (stdout === null) {
|
|
1034
|
+
const head = await runGit(["rev-parse", "--verify", "HEAD"], gitRoot);
|
|
1035
|
+
return head === null ? { entries: [], hasMore: false } : null;
|
|
1036
|
+
}
|
|
1037
|
+
const entries = parseLogRecords(stdout);
|
|
1038
|
+
return { entries: entries.slice(0, limit), hasMore: entries.length > limit };
|
|
1039
|
+
}
|
|
1040
|
+
__name(fetchGitLog, "fetchGitLog");
|
|
1041
|
+
async function searchGitLog(gitRoot, revisions, query, limit, skip) {
|
|
1042
|
+
const window = String(skip + limit + 1);
|
|
1043
|
+
const walk = /* @__PURE__ */ __name((filter) => runGit(
|
|
1044
|
+
[
|
|
1045
|
+
"--no-optional-locks",
|
|
1046
|
+
"log",
|
|
1047
|
+
"-z",
|
|
1048
|
+
`--format=${LOG_FORMAT}`,
|
|
1049
|
+
"--date-order",
|
|
1050
|
+
"-i",
|
|
1051
|
+
"--fixed-strings",
|
|
1052
|
+
filter,
|
|
1053
|
+
"-n",
|
|
1054
|
+
window,
|
|
1055
|
+
...revisions,
|
|
1056
|
+
"--"
|
|
1057
|
+
],
|
|
1058
|
+
gitRoot
|
|
1059
|
+
), "walk");
|
|
1060
|
+
const runs = [walk(`--grep=${query}`), walk(`--author=${query}`)];
|
|
1061
|
+
if (/^[0-9a-f]{4,40}$/i.test(query)) {
|
|
1062
|
+
runs.push(
|
|
1063
|
+
runGit(
|
|
1064
|
+
[
|
|
1065
|
+
"rev-parse",
|
|
1066
|
+
"--verify",
|
|
1067
|
+
"--quiet",
|
|
1068
|
+
"--end-of-options",
|
|
1069
|
+
`${query}^{commit}`
|
|
1070
|
+
],
|
|
1071
|
+
gitRoot
|
|
1072
|
+
).then(
|
|
1073
|
+
(sha) => sha ? runGit(
|
|
1074
|
+
[
|
|
1075
|
+
"--no-optional-locks",
|
|
1076
|
+
"log",
|
|
1077
|
+
"-z",
|
|
1078
|
+
`--format=${LOG_FORMAT}`,
|
|
1079
|
+
"--no-walk",
|
|
1080
|
+
sha.trim(),
|
|
1081
|
+
"--"
|
|
1082
|
+
],
|
|
1083
|
+
gitRoot
|
|
1084
|
+
) : null
|
|
1085
|
+
)
|
|
1086
|
+
);
|
|
1087
|
+
}
|
|
1088
|
+
const bySha = /* @__PURE__ */ new Map();
|
|
1089
|
+
for (const stdout of await Promise.all(runs)) {
|
|
1090
|
+
for (const entry of parseLogRecords(stdout ?? "")) {
|
|
1091
|
+
if (!bySha.has(entry.sha))
|
|
1092
|
+
bySha.set(entry.sha, { entry, rank: bySha.size });
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1095
|
+
const entries = [...bySha.values()].sort((a, b) => b.entry.commitDate - a.entry.commitDate || a.rank - b.rank).map((item) => item.entry);
|
|
1096
|
+
return {
|
|
1097
|
+
entries: entries.slice(skip, skip + limit),
|
|
1098
|
+
hasMore: entries.length > skip + limit
|
|
1099
|
+
};
|
|
1100
|
+
}
|
|
1101
|
+
__name(searchGitLog, "searchGitLog");
|
|
1102
|
+
async function fetchGitCommitDetail(cwd, sha) {
|
|
1103
|
+
if (!/^[0-9a-f]{7,40}$/i.test(sha)) return null;
|
|
1104
|
+
const gitRoot = findGitRoot(cwd);
|
|
1105
|
+
if (!gitRoot) return null;
|
|
1106
|
+
const metaRaw = await runGit(
|
|
1107
|
+
[
|
|
1108
|
+
"--no-optional-locks",
|
|
1109
|
+
"log",
|
|
1110
|
+
"-1",
|
|
1111
|
+
"-z",
|
|
1112
|
+
`--format=${LOG_DETAIL_FORMAT}`,
|
|
1113
|
+
sha
|
|
1114
|
+
],
|
|
1115
|
+
gitRoot
|
|
1116
|
+
);
|
|
1117
|
+
if (metaRaw === null) return null;
|
|
1118
|
+
const parts = metaRaw.split("\0");
|
|
1119
|
+
if (parts.at(-1) === "") parts.pop();
|
|
1120
|
+
if (parts.length !== 9) return null;
|
|
1121
|
+
const parents = parts[7] ? parts[7].split(" ").filter(Boolean) : [];
|
|
1122
|
+
const diffTreeArgs = parents.length > 1 ? [
|
|
1123
|
+
"--no-optional-locks",
|
|
1124
|
+
"diff-tree",
|
|
1125
|
+
"--no-commit-id",
|
|
1126
|
+
"--numstat",
|
|
1127
|
+
// Detect renames so `git mv` counts as one file (by its new path),
|
|
1128
|
+
// matching the main `git diff` path — diff-tree is plumbing and does
|
|
1129
|
+
// NOT honour diff.renames, so without -M a rename splits into a
|
|
1130
|
+
// delete + add pair. The three-token `-z` rename sequence it then
|
|
1131
|
+
// emits is handled by the pending-rename state machine below.
|
|
1132
|
+
"-M",
|
|
1133
|
+
"-r",
|
|
1134
|
+
"-z",
|
|
1135
|
+
`${sha}^1`,
|
|
1136
|
+
sha
|
|
1137
|
+
] : [
|
|
1138
|
+
"--no-optional-locks",
|
|
1139
|
+
"diff-tree",
|
|
1140
|
+
"--no-commit-id",
|
|
1141
|
+
"--numstat",
|
|
1142
|
+
// Detect renames so `git mv` counts as one file (by its new path),
|
|
1143
|
+
// matching the main `git diff` path — diff-tree is plumbing and does
|
|
1144
|
+
// NOT honour diff.renames, so without -M a rename splits into a
|
|
1145
|
+
// delete + add pair. The three-token `-z` rename sequence it then
|
|
1146
|
+
// emits is handled by the pending-rename state machine below.
|
|
1147
|
+
"-M",
|
|
1148
|
+
"-r",
|
|
1149
|
+
"-z",
|
|
1150
|
+
"--root",
|
|
1151
|
+
sha
|
|
1152
|
+
];
|
|
1153
|
+
const numstatRaw = await runGit(diffTreeArgs, gitRoot);
|
|
1154
|
+
const files = [];
|
|
1155
|
+
let filesCount = 0;
|
|
1156
|
+
let linesAdded = 0;
|
|
1157
|
+
let linesRemoved = 0;
|
|
1158
|
+
if (numstatRaw) {
|
|
1159
|
+
forEachNumstatEntry(numstatRaw, (entry) => {
|
|
1160
|
+
filesCount++;
|
|
1161
|
+
linesAdded += entry.added;
|
|
1162
|
+
linesRemoved += entry.removed;
|
|
1163
|
+
if (files.length < MAX_FILES) {
|
|
1164
|
+
files.push({
|
|
1165
|
+
path: entry.path,
|
|
1166
|
+
added: entry.added,
|
|
1167
|
+
removed: entry.removed,
|
|
1168
|
+
isBinary: entry.isBinary
|
|
1169
|
+
});
|
|
1170
|
+
}
|
|
1171
|
+
});
|
|
1172
|
+
}
|
|
1173
|
+
return {
|
|
1174
|
+
sha: parts[0],
|
|
1175
|
+
shortSha: parts[1],
|
|
1176
|
+
authorName: parts[2],
|
|
1177
|
+
authorEmail: parts[3],
|
|
1178
|
+
authorDate: parseInt(parts[4], 10) || 0,
|
|
1179
|
+
subject: parts[5],
|
|
1180
|
+
refs: parts[6],
|
|
1181
|
+
parents,
|
|
1182
|
+
body: parts[8].replace(/\n$/, ""),
|
|
1183
|
+
files,
|
|
1184
|
+
filesCount,
|
|
1185
|
+
linesAdded,
|
|
1186
|
+
linesRemoved,
|
|
1187
|
+
hiddenCount: Math.max(filesCount - files.length, 0)
|
|
1188
|
+
};
|
|
1189
|
+
}
|
|
1190
|
+
__name(fetchGitCommitDetail, "fetchGitCommitDetail");
|
|
1191
|
+
|
|
1192
|
+
// packages/core/src/utils/gitDirect.ts
|
|
1193
|
+
var SHORT_SHA_LENGTH = 7;
|
|
1194
|
+
var debug = createDebugLogger("gitDirect");
|
|
1195
|
+
var MAX_REF_COMPONENT_LENGTH = 255;
|
|
1196
|
+
var INVALID_REF_CHARS = /[\x00-\x20\x7f-\x9f\u200b-\u200d\u2028\u2029\u202a-\u202e\u2066-\u2069\ufeff~^:?*[\\]/;
|
|
1197
|
+
function isValidRefName(name) {
|
|
1198
|
+
if (!name || name === "HEAD") return false;
|
|
1199
|
+
if (name.startsWith("/") || name.endsWith("/")) return false;
|
|
1200
|
+
if (name.startsWith(".") || name.endsWith(".")) return false;
|
|
1201
|
+
if (name.endsWith(".lock")) return false;
|
|
1202
|
+
if (name.includes("..") || name.includes("//")) return false;
|
|
1203
|
+
if (name.includes("/.") || name.includes("./") || name.includes(".lock/")) {
|
|
1204
|
+
return false;
|
|
1205
|
+
}
|
|
1206
|
+
if (name.includes("@{")) return false;
|
|
1207
|
+
if (INVALID_REF_CHARS.test(name)) return false;
|
|
1208
|
+
if (name.split("/").some((c) => c.length > MAX_REF_COMPONENT_LENGTH)) {
|
|
1209
|
+
return false;
|
|
1210
|
+
}
|
|
1211
|
+
return true;
|
|
1212
|
+
}
|
|
1213
|
+
__name(isValidRefName, "isValidRefName");
|
|
1214
|
+
function isValidGitSha(value) {
|
|
1215
|
+
return /^[0-9a-f]{40}$/.test(value) || /^[0-9a-f]{64}$/.test(value);
|
|
1216
|
+
}
|
|
1217
|
+
__name(isValidGitSha, "isValidGitSha");
|
|
1218
|
+
var gitDirCache = /* @__PURE__ */ new Map();
|
|
1219
|
+
async function isDir(p) {
|
|
1220
|
+
try {
|
|
1221
|
+
return (await fsPromises.stat(p)).isDirectory();
|
|
1222
|
+
} catch {
|
|
1223
|
+
return false;
|
|
1224
|
+
}
|
|
1225
|
+
}
|
|
1226
|
+
__name(isDir, "isDir");
|
|
1227
|
+
async function hasGitStore(dir) {
|
|
1228
|
+
const [objects, refs] = await Promise.all([
|
|
1229
|
+
isDir(path3.join(dir, "objects")),
|
|
1230
|
+
isDir(path3.join(dir, "refs"))
|
|
1231
|
+
]);
|
|
1232
|
+
return objects && refs;
|
|
1233
|
+
}
|
|
1234
|
+
__name(hasGitStore, "hasGitStore");
|
|
1235
|
+
async function isRealGitDir(gitDir) {
|
|
1236
|
+
if (await hasGitStore(gitDir)) return true;
|
|
1237
|
+
const rel = await readFirstLineNoFollow(path3.join(gitDir, "commondir"));
|
|
1238
|
+
if (!rel) return false;
|
|
1239
|
+
return hasGitStore(path3.resolve(gitDir, rel.trim()));
|
|
1240
|
+
}
|
|
1241
|
+
__name(isRealGitDir, "isRealGitDir");
|
|
1242
|
+
async function resolveTrustedGitDir(cwd) {
|
|
1243
|
+
const gitDir = await resolveGitDir(cwd);
|
|
1244
|
+
if (!gitDir) return null;
|
|
1245
|
+
return await isRealGitDir(gitDir) ? gitDir : null;
|
|
1246
|
+
}
|
|
1247
|
+
__name(resolveTrustedGitDir, "resolveTrustedGitDir");
|
|
1248
|
+
async function getCachedGitDir(cwd) {
|
|
1249
|
+
const key = path3.resolve(cwd);
|
|
1250
|
+
const cached = gitDirCache.get(key);
|
|
1251
|
+
if (cached !== void 0) return cached;
|
|
1252
|
+
const gitDir = await resolveTrustedGitDir(key);
|
|
1253
|
+
if (gitDir !== null) gitDirCache.set(key, gitDir);
|
|
1254
|
+
return gitDir;
|
|
1255
|
+
}
|
|
1256
|
+
__name(getCachedGitDir, "getCachedGitDir");
|
|
1257
|
+
async function readGitHead(gitDir) {
|
|
1258
|
+
const firstLine = await readFirstLineNoFollow(path3.join(gitDir, "HEAD"));
|
|
1259
|
+
if (firstLine === null) return null;
|
|
1260
|
+
const content = firstLine.trim();
|
|
1261
|
+
if (content.startsWith("ref:")) {
|
|
1262
|
+
const ref = content.slice(4).trim();
|
|
1263
|
+
if (!ref.startsWith("refs/heads/")) return null;
|
|
1264
|
+
const name = ref.slice("refs/heads/".length);
|
|
1265
|
+
if (!isValidRefName(name)) return null;
|
|
1266
|
+
return { type: "branch", name };
|
|
1267
|
+
}
|
|
1268
|
+
if (isValidGitSha(content)) {
|
|
1269
|
+
return { type: "detached", name: content };
|
|
1270
|
+
}
|
|
1271
|
+
return null;
|
|
1272
|
+
}
|
|
1273
|
+
__name(readGitHead, "readGitHead");
|
|
1274
|
+
async function resolveBranchName(cwd) {
|
|
1275
|
+
const gitDir = await getCachedGitDir(cwd);
|
|
1276
|
+
if (!gitDir) return void 0;
|
|
1277
|
+
const head = await readGitHead(gitDir);
|
|
1278
|
+
if (!head) return void 0;
|
|
1279
|
+
return head.type === "branch" ? head.name : head.name.slice(0, SHORT_SHA_LENGTH);
|
|
1280
|
+
}
|
|
1281
|
+
__name(resolveBranchName, "resolveBranchName");
|
|
1282
|
+
var repoBranchWatches = /* @__PURE__ */ new Map();
|
|
1283
|
+
async function watchRepoBranch(cwd, onChange) {
|
|
1284
|
+
const gitDir = await getCachedGitDir(cwd);
|
|
1285
|
+
if (!gitDir) return () => {
|
|
1286
|
+
};
|
|
1287
|
+
let entry = repoBranchWatches.get(gitDir);
|
|
1288
|
+
if (!entry) {
|
|
1289
|
+
const logsHeadPath = path3.join(gitDir, "logs", "HEAD");
|
|
1290
|
+
try {
|
|
1291
|
+
await fsPromises.access(logsHeadPath, fs2.constants?.F_OK ?? 0);
|
|
1292
|
+
if ((await fsPromises.lstat(logsHeadPath)).isSymbolicLink()) {
|
|
1293
|
+
return () => {
|
|
1294
|
+
};
|
|
1295
|
+
}
|
|
1296
|
+
} catch {
|
|
1297
|
+
return () => {
|
|
1298
|
+
};
|
|
1299
|
+
}
|
|
1300
|
+
const existing = repoBranchWatches.get(gitDir);
|
|
1301
|
+
if (existing) {
|
|
1302
|
+
entry = existing;
|
|
1303
|
+
} else {
|
|
1304
|
+
let watcher;
|
|
1305
|
+
try {
|
|
1306
|
+
watcher = fs2.watch(logsHeadPath, (eventType) => {
|
|
1307
|
+
if (eventType === "change" || eventType === "rename") {
|
|
1308
|
+
repoBranchWatches.get(gitDir)?.subscribers.forEach((cb) => {
|
|
1309
|
+
try {
|
|
1310
|
+
cb();
|
|
1311
|
+
} catch {
|
|
1312
|
+
}
|
|
1313
|
+
});
|
|
1314
|
+
}
|
|
1315
|
+
});
|
|
1316
|
+
} catch (err) {
|
|
1317
|
+
debug.warn(`failed to watch reflog for ${gitDir}: ${err}`);
|
|
1318
|
+
return () => {
|
|
1319
|
+
};
|
|
1320
|
+
}
|
|
1321
|
+
watcher.on("error", (err) => {
|
|
1322
|
+
debug.warn(
|
|
1323
|
+
`reflog watcher error for ${gitDir}; branch will no longer auto-update: ${err?.message}`
|
|
1324
|
+
);
|
|
1325
|
+
const current = repoBranchWatches.get(gitDir);
|
|
1326
|
+
if (current?.watcher === watcher) {
|
|
1327
|
+
try {
|
|
1328
|
+
watcher.close();
|
|
1329
|
+
} catch {
|
|
1330
|
+
}
|
|
1331
|
+
repoBranchWatches.delete(gitDir);
|
|
1332
|
+
}
|
|
1333
|
+
});
|
|
1334
|
+
entry = { watcher, subscribers: /* @__PURE__ */ new Set() };
|
|
1335
|
+
repoBranchWatches.set(gitDir, entry);
|
|
1336
|
+
}
|
|
1337
|
+
}
|
|
1338
|
+
entry.subscribers.add(onChange);
|
|
1339
|
+
let disposed = false;
|
|
1340
|
+
return () => {
|
|
1341
|
+
if (disposed) return;
|
|
1342
|
+
disposed = true;
|
|
1343
|
+
const e = repoBranchWatches.get(gitDir);
|
|
1344
|
+
if (!e) return;
|
|
1345
|
+
e.subscribers.delete(onChange);
|
|
1346
|
+
if (e.subscribers.size === 0) {
|
|
1347
|
+
e.watcher.close();
|
|
1348
|
+
repoBranchWatches.delete(gitDir);
|
|
1349
|
+
}
|
|
1350
|
+
};
|
|
1351
|
+
}
|
|
1352
|
+
__name(watchRepoBranch, "watchRepoBranch");
|
|
1353
|
+
|
|
1354
|
+
// packages/core/src/utils/git-branches.ts
|
|
1355
|
+
var debugLogger = createDebugLogger("GIT_BRANCHES");
|
|
1356
|
+
var execFileAsync2 = promisify2(execFile2);
|
|
1357
|
+
var GIT_TIMEOUT_MS2 = 3e4;
|
|
1358
|
+
var MAX_RECENT_BRANCHES = 20;
|
|
1359
|
+
var MAX_REFLOG_ENTRIES = 200;
|
|
1360
|
+
var GIT_ENV_VARS_TO_CLEAR = [
|
|
1361
|
+
"GIT_DIR",
|
|
1362
|
+
"GIT_WORK_TREE",
|
|
1363
|
+
"GIT_COMMON_DIR",
|
|
1364
|
+
"GIT_INDEX_FILE",
|
|
1365
|
+
"GIT_CONFIG_GLOBAL",
|
|
1366
|
+
"GIT_CONFIG_SYSTEM",
|
|
1367
|
+
"GIT_CONFIG_NOSYSTEM",
|
|
1368
|
+
// Repository selectors that an inherited daemon environment could use to
|
|
1369
|
+
// redirect a trusted-workspace git/gh invocation to a different repository
|
|
1370
|
+
// or object database despite the resolved cwd.
|
|
1371
|
+
"GH_REPO",
|
|
1372
|
+
"GIT_CONFIG_COUNT",
|
|
1373
|
+
"GIT_CONFIG_PARAMETERS",
|
|
1374
|
+
"GIT_OBJECT_DIRECTORY",
|
|
1375
|
+
"GIT_ALTERNATE_OBJECT_DIRECTORIES"
|
|
1376
|
+
];
|
|
1377
|
+
var GIT_ENV_PREFIXES_TO_CLEAR = ["GIT_CONFIG_KEY_", "GIT_CONFIG_VALUE_"];
|
|
1378
|
+
var HELPER_PROTOCOLS = /* @__PURE__ */ new Set(["ext", "fd"]);
|
|
1379
|
+
function gitEnv(base) {
|
|
1380
|
+
const env = { ...base ?? process.env };
|
|
1381
|
+
for (const key of GIT_ENV_VARS_TO_CLEAR) {
|
|
1382
|
+
delete env[key];
|
|
1383
|
+
}
|
|
1384
|
+
for (const key of Object.keys(env)) {
|
|
1385
|
+
if (GIT_ENV_PREFIXES_TO_CLEAR.some((prefix) => key.startsWith(prefix))) {
|
|
1386
|
+
delete env[key];
|
|
1387
|
+
}
|
|
1388
|
+
}
|
|
1389
|
+
const inheritedAllow = env["GIT_ALLOW_PROTOCOL"];
|
|
1390
|
+
if (inheritedAllow !== void 0) {
|
|
1391
|
+
env["GIT_ALLOW_PROTOCOL"] = inheritedAllow.split(":").filter((p) => !HELPER_PROTOCOLS.has(p.trim().toLowerCase())).join(":");
|
|
1392
|
+
}
|
|
1393
|
+
env["LC_ALL"] = "C";
|
|
1394
|
+
env["LANG"] = "C";
|
|
1395
|
+
return env;
|
|
1396
|
+
}
|
|
1397
|
+
__name(gitEnv, "gitEnv");
|
|
1398
|
+
function runGit2(cwd, args, env) {
|
|
1399
|
+
return execFileAsync2("git", args, {
|
|
1400
|
+
cwd,
|
|
1401
|
+
timeout: GIT_TIMEOUT_MS2,
|
|
1402
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
1403
|
+
env: gitEnv(env)
|
|
1404
|
+
}).then(({ stdout }) => stdout);
|
|
1405
|
+
}
|
|
1406
|
+
__name(runGit2, "runGit");
|
|
1407
|
+
var SEPARATOR = "\0";
|
|
1408
|
+
async function fetchGitBranches(cwd, env) {
|
|
1409
|
+
await runGit2(cwd, ["rev-parse", "--git-dir"], env);
|
|
1410
|
+
const [localRaw, remoteRaw, tagsRaw, headRaw, reflogRaw] = await Promise.all([
|
|
1411
|
+
runGit2(
|
|
1412
|
+
cwd,
|
|
1413
|
+
[
|
|
1414
|
+
"for-each-ref",
|
|
1415
|
+
"--format=%(refname:short)%00%(HEAD)%00%(upstream:short)%00%(upstream:track,nobracket)%00%(committerdate:unix)%00%(subject)%00%(symref)%00%(push:short)%00%(push:track,nobracket)",
|
|
1416
|
+
"refs/heads/"
|
|
1417
|
+
],
|
|
1418
|
+
env
|
|
1419
|
+
).catch(() => ""),
|
|
1420
|
+
runGit2(
|
|
1421
|
+
cwd,
|
|
1422
|
+
[
|
|
1423
|
+
"for-each-ref",
|
|
1424
|
+
"--format=%(refname:short)%00%(HEAD)%00%(upstream:short)%00%(upstream:track,nobracket)%00%(committerdate:unix)%00%(subject)%00%(symref)%00%(push:short)%00%(push:track,nobracket)",
|
|
1425
|
+
"refs/remotes/"
|
|
1426
|
+
],
|
|
1427
|
+
env
|
|
1428
|
+
).catch(() => ""),
|
|
1429
|
+
runGit2(
|
|
1430
|
+
cwd,
|
|
1431
|
+
[
|
|
1432
|
+
"for-each-ref",
|
|
1433
|
+
"--format=%(refname:short)%00%(creatordate:unix)%00%(subject)",
|
|
1434
|
+
"--sort=-creatordate",
|
|
1435
|
+
"refs/tags/"
|
|
1436
|
+
],
|
|
1437
|
+
env
|
|
1438
|
+
).catch(() => ""),
|
|
1439
|
+
runGit2(cwd, ["symbolic-ref", "--short", "HEAD"], env).catch(() => ""),
|
|
1440
|
+
runGit2(
|
|
1441
|
+
cwd,
|
|
1442
|
+
["reflog", "show", "--format=%gs", `-${MAX_REFLOG_ENTRIES}`],
|
|
1443
|
+
env
|
|
1444
|
+
).catch(() => "")
|
|
1445
|
+
]);
|
|
1446
|
+
const local = parseBranchLines(localRaw);
|
|
1447
|
+
const remote = parseBranchLines(remoteRaw);
|
|
1448
|
+
const tags = parseTagLines(tagsRaw);
|
|
1449
|
+
const recent = parseRecentBranches(reflogRaw, headRaw.trim());
|
|
1450
|
+
const headTrimmed = headRaw.trim();
|
|
1451
|
+
const detached = !headTrimmed;
|
|
1452
|
+
return {
|
|
1453
|
+
local,
|
|
1454
|
+
remote,
|
|
1455
|
+
tags,
|
|
1456
|
+
recent,
|
|
1457
|
+
head: headTrimmed || await getDetachedHead(cwd, env),
|
|
1458
|
+
detached
|
|
1459
|
+
};
|
|
1460
|
+
}
|
|
1461
|
+
__name(fetchGitBranches, "fetchGitBranches");
|
|
1462
|
+
function parseBranchLines(raw) {
|
|
1463
|
+
if (!raw.trim()) return [];
|
|
1464
|
+
return raw.trim().split("\n").filter(Boolean).filter((line) => {
|
|
1465
|
+
const parts = line.split(SEPARATOR);
|
|
1466
|
+
return !(parts[6] ?? "");
|
|
1467
|
+
}).map((line) => {
|
|
1468
|
+
const parts = line.split(SEPARATOR);
|
|
1469
|
+
const name = parts[0] ?? "";
|
|
1470
|
+
const isHead = parts[1] === "*";
|
|
1471
|
+
const upstream = parts[2] || void 0;
|
|
1472
|
+
const track = parts[3] ?? "";
|
|
1473
|
+
const commitDate = parseInt(parts[4] ?? "0", 10) || 0;
|
|
1474
|
+
const commitSubject = parts[5] ?? "";
|
|
1475
|
+
let ahead = 0;
|
|
1476
|
+
let behind = 0;
|
|
1477
|
+
const aheadMatch = /ahead (\d+)/.exec(track);
|
|
1478
|
+
const behindMatch = /behind (\d+)/.exec(track);
|
|
1479
|
+
if (aheadMatch) ahead = parseInt(aheadMatch[1], 10);
|
|
1480
|
+
if (behindMatch) behind = parseInt(behindMatch[1], 10);
|
|
1481
|
+
const upstreamGone = upstream !== void 0 && /\bgone\b/.test(track);
|
|
1482
|
+
const pushTarget = parts[7] || void 0;
|
|
1483
|
+
const pushTrack = parts[8] ?? "";
|
|
1484
|
+
const pushGone = pushTarget !== void 0 && /\bgone\b/.test(pushTrack);
|
|
1485
|
+
let pushAhead;
|
|
1486
|
+
let pushBehind;
|
|
1487
|
+
if (pushTarget !== void 0 && !pushGone) {
|
|
1488
|
+
const pa = /ahead (\d+)/.exec(pushTrack);
|
|
1489
|
+
const pb = /behind (\d+)/.exec(pushTrack);
|
|
1490
|
+
pushAhead = pa ? parseInt(pa[1], 10) : 0;
|
|
1491
|
+
pushBehind = pb ? parseInt(pb[1], 10) : 0;
|
|
1492
|
+
}
|
|
1493
|
+
return {
|
|
1494
|
+
name,
|
|
1495
|
+
isHead,
|
|
1496
|
+
upstream,
|
|
1497
|
+
...upstreamGone ? { upstreamGone } : {},
|
|
1498
|
+
ahead,
|
|
1499
|
+
behind,
|
|
1500
|
+
...pushTarget !== void 0 ? { pushTarget } : {},
|
|
1501
|
+
...pushAhead !== void 0 ? { pushAhead } : {},
|
|
1502
|
+
...pushBehind !== void 0 ? { pushBehind } : {},
|
|
1503
|
+
...pushGone ? { pushGone } : {},
|
|
1504
|
+
commitDate,
|
|
1505
|
+
commitSubject
|
|
1506
|
+
};
|
|
1507
|
+
});
|
|
1508
|
+
}
|
|
1509
|
+
__name(parseBranchLines, "parseBranchLines");
|
|
1510
|
+
function parseTagLines(raw) {
|
|
1511
|
+
if (!raw.trim()) return [];
|
|
1512
|
+
return raw.trim().split("\n").filter(Boolean).map((line) => {
|
|
1513
|
+
const parts = line.split(SEPARATOR);
|
|
1514
|
+
return {
|
|
1515
|
+
name: parts[0] ?? "",
|
|
1516
|
+
date: parseInt(parts[1] ?? "0", 10) || 0,
|
|
1517
|
+
subject: parts[2] ?? ""
|
|
1518
|
+
};
|
|
1519
|
+
});
|
|
1520
|
+
}
|
|
1521
|
+
__name(parseTagLines, "parseTagLines");
|
|
1522
|
+
function parseRecentBranches(reflogRaw, currentHead) {
|
|
1523
|
+
if (!reflogRaw.trim()) return [];
|
|
1524
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1525
|
+
const result = [];
|
|
1526
|
+
for (const line of reflogRaw.trim().split("\n")) {
|
|
1527
|
+
if (!line.startsWith("checkout: moving from ")) continue;
|
|
1528
|
+
const idx = line.indexOf(" to ");
|
|
1529
|
+
if (idx === -1) continue;
|
|
1530
|
+
const branch = line.slice(idx + 4);
|
|
1531
|
+
if (branch && !seen.has(branch) && branch !== currentHead && !/^[0-9a-f]{7,40}$/.test(branch)) {
|
|
1532
|
+
seen.add(branch);
|
|
1533
|
+
result.push(branch);
|
|
1534
|
+
if (result.length >= MAX_RECENT_BRANCHES) break;
|
|
1535
|
+
}
|
|
1536
|
+
}
|
|
1537
|
+
return result;
|
|
1538
|
+
}
|
|
1539
|
+
__name(parseRecentBranches, "parseRecentBranches");
|
|
1540
|
+
async function getDetachedHead(cwd, env) {
|
|
1541
|
+
try {
|
|
1542
|
+
const sha = await runGit2(cwd, ["rev-parse", "--short", "HEAD"], env);
|
|
1543
|
+
return sha.trim();
|
|
1544
|
+
} catch {
|
|
1545
|
+
return "";
|
|
1546
|
+
}
|
|
1547
|
+
}
|
|
1548
|
+
__name(getDetachedHead, "getDetachedHead");
|
|
1549
|
+
function isValidCheckoutRef(value) {
|
|
1550
|
+
const ref = value.trim();
|
|
1551
|
+
if (!ref || ref.startsWith("-")) return false;
|
|
1552
|
+
if (ref === "HEAD") return true;
|
|
1553
|
+
return isValidRefName(ref) || isValidGitSha(ref);
|
|
1554
|
+
}
|
|
1555
|
+
__name(isValidCheckoutRef, "isValidCheckoutRef");
|
|
1556
|
+
async function gitCheckout(cwd, ref, env) {
|
|
1557
|
+
if (!isValidCheckoutRef(ref)) {
|
|
1558
|
+
throw new Error(`invalid checkout ref: ${ref}`);
|
|
1559
|
+
}
|
|
1560
|
+
const isRemoteTracking = await runGit2(
|
|
1561
|
+
cwd,
|
|
1562
|
+
["show-ref", "--verify", "--quiet", `refs/remotes/${ref}`],
|
|
1563
|
+
env
|
|
1564
|
+
).then(() => true).catch(() => false);
|
|
1565
|
+
if (isRemoteTracking) {
|
|
1566
|
+
const localName = ref.slice(ref.indexOf("/") + 1);
|
|
1567
|
+
if (!isValidCheckoutRef(localName)) {
|
|
1568
|
+
throw new Error(
|
|
1569
|
+
`invalid local branch name derived from remote ref: ${localName}`
|
|
1570
|
+
);
|
|
1571
|
+
}
|
|
1572
|
+
const hasLocal = await runGit2(
|
|
1573
|
+
cwd,
|
|
1574
|
+
["show-ref", "--verify", "--quiet", `refs/heads/${localName}`],
|
|
1575
|
+
env
|
|
1576
|
+
).then(() => true).catch(() => false);
|
|
1577
|
+
if (hasLocal) {
|
|
1578
|
+
await runGit2(cwd, ["checkout", localName, "--"], env);
|
|
1579
|
+
} else {
|
|
1580
|
+
await runGit2(cwd, ["checkout", "--track", ref], env);
|
|
1581
|
+
}
|
|
1582
|
+
const head = (await runGit2(cwd, ["symbolic-ref", "--short", "HEAD"], env)).trim();
|
|
1583
|
+
return { branch: head, detached: false };
|
|
1584
|
+
}
|
|
1585
|
+
await runGit2(cwd, ["checkout", ref, "--"], env);
|
|
1586
|
+
const headRaw = await runGit2(
|
|
1587
|
+
cwd,
|
|
1588
|
+
["symbolic-ref", "--short", "HEAD"],
|
|
1589
|
+
env
|
|
1590
|
+
).catch(() => "");
|
|
1591
|
+
const trimmed = headRaw.trim();
|
|
1592
|
+
if (trimmed) {
|
|
1593
|
+
return { branch: trimmed, detached: false };
|
|
1594
|
+
}
|
|
1595
|
+
const sha = await runGit2(cwd, ["rev-parse", "--short", "HEAD"], env);
|
|
1596
|
+
return { branch: sha.trim(), detached: true };
|
|
1597
|
+
}
|
|
1598
|
+
__name(gitCheckout, "gitCheckout");
|
|
1599
|
+
async function gitCreateBranch(cwd, name, startPoint, env) {
|
|
1600
|
+
if (!isValidRefName(name) || name.startsWith("-")) {
|
|
1601
|
+
throw new Error(`invalid branch name: ${name}`);
|
|
1602
|
+
}
|
|
1603
|
+
const args = ["checkout", "-b", name];
|
|
1604
|
+
if (startPoint) {
|
|
1605
|
+
if (!isValidCheckoutRef(startPoint)) {
|
|
1606
|
+
throw new Error(`invalid start point: ${startPoint}`);
|
|
1607
|
+
}
|
|
1608
|
+
args.push(startPoint);
|
|
1609
|
+
}
|
|
1610
|
+
args.push("--");
|
|
1611
|
+
const originalRef = (await runGit2(
|
|
1612
|
+
cwd,
|
|
1613
|
+
["symbolic-ref", "--quiet", "--short", "HEAD"],
|
|
1614
|
+
env
|
|
1615
|
+
).catch(() => "")).trim();
|
|
1616
|
+
const originalCommit = originalRef ? "" : (await runGit2(cwd, ["rev-parse", "HEAD"], env).catch(() => "")).trim();
|
|
1617
|
+
const startCommit = (await runGit2(
|
|
1618
|
+
cwd,
|
|
1619
|
+
["rev-parse", "--verify", `${startPoint || "HEAD"}^{commit}`],
|
|
1620
|
+
env
|
|
1621
|
+
).catch(() => "")).trim();
|
|
1622
|
+
try {
|
|
1623
|
+
await runGit2(cwd, args, env);
|
|
1624
|
+
} catch (err) {
|
|
1625
|
+
const nowOn = (await runGit2(
|
|
1626
|
+
cwd,
|
|
1627
|
+
["symbolic-ref", "--quiet", "--short", "HEAD"],
|
|
1628
|
+
env
|
|
1629
|
+
).catch(() => "")).trim();
|
|
1630
|
+
if (nowOn === name) {
|
|
1631
|
+
if (originalRef) {
|
|
1632
|
+
await runGit2(cwd, ["checkout", originalRef, "--"], env).catch(() => {
|
|
1633
|
+
});
|
|
1634
|
+
} else if (originalCommit) {
|
|
1635
|
+
await runGit2(
|
|
1636
|
+
cwd,
|
|
1637
|
+
["checkout", "--detach", originalCommit, "--"],
|
|
1638
|
+
env
|
|
1639
|
+
).catch(() => {
|
|
1640
|
+
});
|
|
1641
|
+
}
|
|
1642
|
+
const newHead = (await runGit2(cwd, ["rev-parse", `refs/heads/${name}`], env).catch(
|
|
1643
|
+
() => ""
|
|
1644
|
+
)).trim();
|
|
1645
|
+
if (newHead && newHead !== startCommit) {
|
|
1646
|
+
debugLogger.warn(
|
|
1647
|
+
`gitCreateBranch: keeping branch "${name}" because it contains commits created after checkout (likely by a failing post-checkout hook)`
|
|
1648
|
+
);
|
|
1649
|
+
} else {
|
|
1650
|
+
await runGit2(cwd, ["branch", "-D", name], env).catch(() => {
|
|
1651
|
+
});
|
|
1652
|
+
}
|
|
1653
|
+
}
|
|
1654
|
+
throw err;
|
|
1655
|
+
}
|
|
1656
|
+
return { branch: name, detached: false };
|
|
1657
|
+
}
|
|
1658
|
+
__name(gitCreateBranch, "gitCreateBranch");
|
|
1659
|
+
async function gitPush(cwd, opts, env) {
|
|
1660
|
+
const args = ["push"];
|
|
1661
|
+
if (opts?.force) args.push("--force-with-lease");
|
|
1662
|
+
if (opts?.setUpstream) {
|
|
1663
|
+
let branch;
|
|
1664
|
+
try {
|
|
1665
|
+
branch = (await runGit2(cwd, ["symbolic-ref", "--short", "HEAD"], env)).trim();
|
|
1666
|
+
} catch {
|
|
1667
|
+
throw new Error(
|
|
1668
|
+
"cannot push with --set-upstream in detached HEAD state; check out a branch first"
|
|
1669
|
+
);
|
|
1670
|
+
}
|
|
1671
|
+
const hasUpstream = await runGit2(
|
|
1672
|
+
cwd,
|
|
1673
|
+
["rev-parse", "--abbrev-ref", `${branch}@{u}`],
|
|
1674
|
+
env
|
|
1675
|
+
).catch(() => "");
|
|
1676
|
+
if (hasUpstream.trim()) {
|
|
1677
|
+
const output2 = await runGit2(cwd, args, env);
|
|
1678
|
+
return { success: true, output: output2.trim() };
|
|
1679
|
+
}
|
|
1680
|
+
let remote = (await runGit2(cwd, ["config", `branch.${branch}.pushRemote`], env).catch(
|
|
1681
|
+
() => ""
|
|
1682
|
+
)).trim();
|
|
1683
|
+
if (!remote) {
|
|
1684
|
+
remote = (await runGit2(cwd, ["config", "remote.pushDefault"], env).catch(() => "")).trim();
|
|
1685
|
+
}
|
|
1686
|
+
if (!remote) {
|
|
1687
|
+
remote = (await runGit2(cwd, ["config", `branch.${branch}.remote`], env).catch(
|
|
1688
|
+
() => ""
|
|
1689
|
+
)).trim();
|
|
1690
|
+
}
|
|
1691
|
+
if (!remote) {
|
|
1692
|
+
const remotes = (await runGit2(cwd, ["remote"], env).catch(() => "")).trim();
|
|
1693
|
+
const remoteList = remotes ? remotes.split("\n") : [];
|
|
1694
|
+
remote = remoteList.length === 1 ? remoteList[0] ?? "origin" : "origin";
|
|
1695
|
+
}
|
|
1696
|
+
args.push("--set-upstream", remote, branch);
|
|
1697
|
+
}
|
|
1698
|
+
const output = await runGit2(cwd, args, env);
|
|
1699
|
+
return { success: true, output: output.trim() };
|
|
1700
|
+
}
|
|
1701
|
+
__name(gitPush, "gitPush");
|
|
1702
|
+
var GitPullFailure = class extends Error {
|
|
1703
|
+
static {
|
|
1704
|
+
__name(this, "GitPullFailure");
|
|
1705
|
+
}
|
|
1706
|
+
code;
|
|
1707
|
+
constructor(code, message) {
|
|
1708
|
+
super(message);
|
|
1709
|
+
this.name = "GitPullFailure";
|
|
1710
|
+
this.code = code;
|
|
1711
|
+
}
|
|
1712
|
+
};
|
|
1713
|
+
var AUTO_STASH_MESSAGE = "o1-code: auto-stash before pull";
|
|
1714
|
+
var OPERATION_STATE_PATHS = [
|
|
1715
|
+
["MERGE_HEAD", "merge"],
|
|
1716
|
+
["CHERRY_PICK_HEAD", "cherry-pick"],
|
|
1717
|
+
["REVERT_HEAD", "revert"],
|
|
1718
|
+
["rebase-merge", "rebase"],
|
|
1719
|
+
["rebase-apply", "rebase or am"]
|
|
1720
|
+
];
|
|
1721
|
+
function gitDetail(err) {
|
|
1722
|
+
if (err && typeof err === "object" && ("stdout" in err || "stderr" in err)) {
|
|
1723
|
+
const e = err;
|
|
1724
|
+
const detail = `${e.stdout ?? ""}
|
|
1725
|
+
${e.stderr ?? ""}`.trim();
|
|
1726
|
+
if (detail) return detail;
|
|
1727
|
+
return "git exited without diagnostic output; a stale lock file (e.g. .git/index.lock) or another git process is the usual cause";
|
|
1728
|
+
}
|
|
1729
|
+
return err instanceof Error ? err.message : String(err);
|
|
1730
|
+
}
|
|
1731
|
+
__name(gitDetail, "gitDetail");
|
|
1732
|
+
function exitCode(err) {
|
|
1733
|
+
const code = err?.code;
|
|
1734
|
+
return typeof code === "number" ? code : void 0;
|
|
1735
|
+
}
|
|
1736
|
+
__name(exitCode, "exitCode");
|
|
1737
|
+
function pullExitCode(err) {
|
|
1738
|
+
const code = exitCode(err);
|
|
1739
|
+
if (code !== void 0) return code;
|
|
1740
|
+
const e = err;
|
|
1741
|
+
if (e && (e.killed === true || typeof e.signal === "string")) return 1;
|
|
1742
|
+
return void 0;
|
|
1743
|
+
}
|
|
1744
|
+
__name(pullExitCode, "pullExitCode");
|
|
1745
|
+
async function gitPaths(cwd, names, env) {
|
|
1746
|
+
const args = ["rev-parse"];
|
|
1747
|
+
for (const name of names) args.push("--git-path", name);
|
|
1748
|
+
return (await runGit2(cwd, args, env)).trim().split("\n").map((line) => path4.resolve(cwd, line));
|
|
1749
|
+
}
|
|
1750
|
+
__name(gitPaths, "gitPaths");
|
|
1751
|
+
async function operationInProgress(cwd, env) {
|
|
1752
|
+
const paths = await gitPaths(
|
|
1753
|
+
cwd,
|
|
1754
|
+
OPERATION_STATE_PATHS.map(([statePath]) => statePath),
|
|
1755
|
+
env
|
|
1756
|
+
);
|
|
1757
|
+
for (let i = 0; i < OPERATION_STATE_PATHS.length; i++) {
|
|
1758
|
+
if (fs3.existsSync(paths[i])) return OPERATION_STATE_PATHS[i][1];
|
|
1759
|
+
}
|
|
1760
|
+
return void 0;
|
|
1761
|
+
}
|
|
1762
|
+
__name(operationInProgress, "operationInProgress");
|
|
1763
|
+
async function refuseOperationInProgress(cwd, env) {
|
|
1764
|
+
const operation = await operationInProgress(cwd, env);
|
|
1765
|
+
if (operation) {
|
|
1766
|
+
throw new GitPullFailure(
|
|
1767
|
+
"operation_in_progress",
|
|
1768
|
+
`cannot update: a ${operation} is in progress; finish or abort it from a terminal first`
|
|
1769
|
+
);
|
|
1770
|
+
}
|
|
1771
|
+
}
|
|
1772
|
+
__name(refuseOperationInProgress, "refuseOperationInProgress");
|
|
1773
|
+
async function upstreamSha(cwd, env, ref = "") {
|
|
1774
|
+
try {
|
|
1775
|
+
return (await runGit2(
|
|
1776
|
+
cwd,
|
|
1777
|
+
["rev-parse", "--verify", "--quiet", `${ref}@{upstream}`],
|
|
1778
|
+
env
|
|
1779
|
+
)).trim();
|
|
1780
|
+
} catch {
|
|
1781
|
+
return "";
|
|
1782
|
+
}
|
|
1783
|
+
}
|
|
1784
|
+
__name(upstreamSha, "upstreamSha");
|
|
1785
|
+
function readTrimmed(file) {
|
|
1786
|
+
try {
|
|
1787
|
+
return fs3.readFileSync(file, "utf8").trim();
|
|
1788
|
+
} catch {
|
|
1789
|
+
return "";
|
|
1790
|
+
}
|
|
1791
|
+
}
|
|
1792
|
+
__name(readTrimmed, "readTrimmed");
|
|
1793
|
+
async function abortOwnPullState(cwd, pullExitCode2, env) {
|
|
1794
|
+
if (pullExitCode2 !== 1) return;
|
|
1795
|
+
try {
|
|
1796
|
+
const [mergeHead, rebaseMergeDir, rebaseApplyDir] = await gitPaths(
|
|
1797
|
+
cwd,
|
|
1798
|
+
["MERGE_HEAD", "rebase-merge", "rebase-apply"],
|
|
1799
|
+
env
|
|
1800
|
+
);
|
|
1801
|
+
if (fs3.existsSync(mergeHead)) {
|
|
1802
|
+
const upstream = await upstreamSha(cwd, env);
|
|
1803
|
+
if (upstream && readTrimmed(mergeHead) === upstream) {
|
|
1804
|
+
await runGit2(cwd, ["merge", "--abort"], env);
|
|
1805
|
+
}
|
|
1806
|
+
return;
|
|
1807
|
+
}
|
|
1808
|
+
for (const dir of [rebaseMergeDir, rebaseApplyDir]) {
|
|
1809
|
+
if (!fs3.existsSync(dir)) continue;
|
|
1810
|
+
const headName = readTrimmed(path4.join(dir, "head-name")).replace(
|
|
1811
|
+
/^refs\/heads\//,
|
|
1812
|
+
""
|
|
1813
|
+
);
|
|
1814
|
+
const upstream = headName ? await upstreamSha(cwd, env, headName) : "";
|
|
1815
|
+
if (upstream && readTrimmed(path4.join(dir, "onto")) === upstream) {
|
|
1816
|
+
await runGit2(cwd, ["rebase", "--abort"], env);
|
|
1817
|
+
}
|
|
1818
|
+
return;
|
|
1819
|
+
}
|
|
1820
|
+
} catch {
|
|
1821
|
+
}
|
|
1822
|
+
}
|
|
1823
|
+
__name(abortOwnPullState, "abortOwnPullState");
|
|
1824
|
+
async function stashEntries(cwd, env) {
|
|
1825
|
+
const raw = await runGit2(cwd, ["stash", "list", "--format=%H%x00%s"], env);
|
|
1826
|
+
return raw.split("\n").filter((line) => line.includes("\0")).map((line) => {
|
|
1827
|
+
const [sha, subject] = line.split("\0");
|
|
1828
|
+
return { sha, subject: subject ?? "" };
|
|
1829
|
+
});
|
|
1830
|
+
}
|
|
1831
|
+
__name(stashEntries, "stashEntries");
|
|
1832
|
+
function parseDroppedStashSha(output) {
|
|
1833
|
+
const match = /\(([0-9a-f]{40,64})\)\s*$/m.exec(output.trim());
|
|
1834
|
+
return match?.[1];
|
|
1835
|
+
}
|
|
1836
|
+
__name(parseDroppedStashSha, "parseDroppedStashSha");
|
|
1837
|
+
async function restoreStash(cwd, sha, env) {
|
|
1838
|
+
let output = "";
|
|
1839
|
+
try {
|
|
1840
|
+
await runGit2(cwd, ["stash", "apply", sha], env);
|
|
1841
|
+
} catch (err) {
|
|
1842
|
+
return { restored: false, output: gitDetail(err) };
|
|
1843
|
+
}
|
|
1844
|
+
let entries;
|
|
1845
|
+
try {
|
|
1846
|
+
entries = await stashEntries(cwd, env);
|
|
1847
|
+
} catch (err) {
|
|
1848
|
+
return {
|
|
1849
|
+
restored: true,
|
|
1850
|
+
output: `restored, but stash entry ${sha} was kept because the stash could not be listed:
|
|
1851
|
+
${gitDetail(err)}`
|
|
1852
|
+
};
|
|
1853
|
+
}
|
|
1854
|
+
const index = entries.findIndex((entry) => entry.sha === sha);
|
|
1855
|
+
if (index === -1) return { restored: true, output };
|
|
1856
|
+
const slot = `stash@{${index}}`;
|
|
1857
|
+
let dropped;
|
|
1858
|
+
try {
|
|
1859
|
+
dropped = parseDroppedStashSha(
|
|
1860
|
+
await runGit2(cwd, ["stash", "drop", slot], env)
|
|
1861
|
+
);
|
|
1862
|
+
} catch (err) {
|
|
1863
|
+
return {
|
|
1864
|
+
restored: true,
|
|
1865
|
+
output: `restored, but stash entry ${slot} (${sha}) could not be dropped:
|
|
1866
|
+
${gitDetail(err)}`
|
|
1867
|
+
};
|
|
1868
|
+
}
|
|
1869
|
+
if (dropped !== void 0 && dropped !== sha) {
|
|
1870
|
+
const subject = (await runGit2(cwd, ["log", "-1", "--format=%s", dropped], env).catch(
|
|
1871
|
+
() => ""
|
|
1872
|
+
)).trim();
|
|
1873
|
+
const storeArgs = ["stash", "store", "--quiet"];
|
|
1874
|
+
if (subject) storeArgs.push("-m", subject);
|
|
1875
|
+
storeArgs.push(dropped);
|
|
1876
|
+
const stored = await runGit2(cwd, storeArgs, env).then(() => true).catch(() => false);
|
|
1877
|
+
output = stored ? `restored; stash entry ${sha} was kept because the stash changed while dropping it` : `restored; stash entry ${sha} was kept because the stash changed while dropping it, and the displaced entry ${dropped} could not be stored back \u2014 recover it with: git stash store ${dropped}`;
|
|
1878
|
+
}
|
|
1879
|
+
return { restored: true, output };
|
|
1880
|
+
}
|
|
1881
|
+
__name(restoreStash, "restoreStash");
|
|
1882
|
+
function pullArgs(opts) {
|
|
1883
|
+
const args = ["pull"];
|
|
1884
|
+
if (opts?.rebase) args.push("--rebase");
|
|
1885
|
+
return args;
|
|
1886
|
+
}
|
|
1887
|
+
__name(pullArgs, "pullArgs");
|
|
1888
|
+
async function validatedUpstream(cwd, env) {
|
|
1889
|
+
const sha = await upstreamSha(cwd, env);
|
|
1890
|
+
if (sha) return sha;
|
|
1891
|
+
let branch;
|
|
1892
|
+
try {
|
|
1893
|
+
branch = (await runGit2(cwd, ["symbolic-ref", "--quiet", "--short", "HEAD"], env)).trim();
|
|
1894
|
+
} catch {
|
|
1895
|
+
throw new GitPullFailure(
|
|
1896
|
+
"pull_failed",
|
|
1897
|
+
"cannot update: HEAD is detached; check out a branch from a terminal first"
|
|
1898
|
+
);
|
|
1899
|
+
}
|
|
1900
|
+
const configured = (await runGit2(
|
|
1901
|
+
cwd,
|
|
1902
|
+
["for-each-ref", "--format=%(upstream)", `refs/heads/${branch}`],
|
|
1903
|
+
env
|
|
1904
|
+
).catch(() => "")).trim();
|
|
1905
|
+
if (configured) {
|
|
1906
|
+
throw new GitPullFailure(
|
|
1907
|
+
"pull_failed",
|
|
1908
|
+
"cannot update: the upstream branch no longer exists on the remote; nothing was changed"
|
|
1909
|
+
);
|
|
1910
|
+
}
|
|
1911
|
+
await runGit2(cwd, ["rev-parse", "--abbrev-ref", "@{upstream}"], env);
|
|
1912
|
+
throw new GitPullFailure(
|
|
1913
|
+
"pull_failed",
|
|
1914
|
+
"cannot update: the upstream branch could not be resolved; nothing was changed"
|
|
1915
|
+
);
|
|
1916
|
+
}
|
|
1917
|
+
__name(validatedUpstream, "validatedUpstream");
|
|
1918
|
+
async function pushAutoStash(cwd, env) {
|
|
1919
|
+
const before = new Set((await stashEntries(cwd, env)).map((e) => e.sha));
|
|
1920
|
+
await refuseOperationInProgress(cwd, env);
|
|
1921
|
+
try {
|
|
1922
|
+
await runGit2(
|
|
1923
|
+
cwd,
|
|
1924
|
+
["stash", "push", "--include-untracked", "-m", AUTO_STASH_MESSAGE],
|
|
1925
|
+
env
|
|
1926
|
+
);
|
|
1927
|
+
} catch (err) {
|
|
1928
|
+
throw new GitPullFailure(
|
|
1929
|
+
"pull_failed",
|
|
1930
|
+
`cannot stash the local changes, so the update was not attempted:
|
|
1931
|
+
${gitDetail(err)}`
|
|
1932
|
+
);
|
|
1933
|
+
}
|
|
1934
|
+
let after;
|
|
1935
|
+
try {
|
|
1936
|
+
after = await stashEntries(cwd, env);
|
|
1937
|
+
} catch (err) {
|
|
1938
|
+
throw new GitPullFailure(
|
|
1939
|
+
"pull_failed",
|
|
1940
|
+
`cannot identify the auto-stash entry, so the update was not attempted; your changes are in the entry labelled "${AUTO_STASH_MESSAGE}" in git stash list:
|
|
1941
|
+
${gitDetail(err)}`
|
|
1942
|
+
);
|
|
1943
|
+
}
|
|
1944
|
+
const created = after.filter(
|
|
1945
|
+
(e) => !before.has(e.sha) && e.subject.endsWith(AUTO_STASH_MESSAGE)
|
|
1946
|
+
);
|
|
1947
|
+
return created.length > 0 ? created[created.length - 1].sha : void 0;
|
|
1948
|
+
}
|
|
1949
|
+
__name(pushAutoStash, "pushAutoStash");
|
|
1950
|
+
async function stashPull(cwd, opts, env) {
|
|
1951
|
+
await refuseOperationInProgress(cwd, env);
|
|
1952
|
+
await runGit2(cwd, ["fetch", "--prune"], env);
|
|
1953
|
+
await validatedUpstream(cwd, env);
|
|
1954
|
+
const stashed = await pushAutoStash(cwd, env);
|
|
1955
|
+
let output;
|
|
1956
|
+
try {
|
|
1957
|
+
output = (await runGit2(cwd, pullArgs(opts), env)).trim();
|
|
1958
|
+
} catch (err) {
|
|
1959
|
+
const detail = gitDetail(err);
|
|
1960
|
+
await abortOwnPullState(cwd, pullExitCode(err), env);
|
|
1961
|
+
if (stashed === void 0) {
|
|
1962
|
+
throw new GitPullFailure("pull_failed", `update failed:
|
|
1963
|
+
${detail}`);
|
|
1964
|
+
}
|
|
1965
|
+
const restore2 = await restoreStash(cwd, stashed, env);
|
|
1966
|
+
throw new GitPullFailure(
|
|
1967
|
+
"pull_failed",
|
|
1968
|
+
restore2.restored ? `update failed; your local changes were restored${restore2.output ? ` (${restore2.output})` : ""}:
|
|
1969
|
+
${detail}` : `update failed and your local changes could not be restored automatically; they are kept in stash entry ${stashed}:
|
|
1970
|
+
${restore2.output}
|
|
1971
|
+
${detail}`
|
|
1972
|
+
);
|
|
1973
|
+
}
|
|
1974
|
+
if (stashed === void 0) return { success: true, output };
|
|
1975
|
+
const restore = await restoreStash(cwd, stashed, env);
|
|
1976
|
+
if (restore.restored) {
|
|
1977
|
+
if (restore.output === "") return { success: true, output };
|
|
1978
|
+
return {
|
|
1979
|
+
success: true,
|
|
1980
|
+
stashKept: true,
|
|
1981
|
+
stashSha: stashed,
|
|
1982
|
+
output: `${output}
|
|
1983
|
+
${restore.output}`.trim()
|
|
1984
|
+
};
|
|
1985
|
+
}
|
|
1986
|
+
return {
|
|
1987
|
+
success: true,
|
|
1988
|
+
stashRestoreConflict: true,
|
|
1989
|
+
stashSha: stashed,
|
|
1990
|
+
output: `${output}
|
|
1991
|
+
restoring your local changes failed; they are kept in stash entry ${stashed}:
|
|
1992
|
+
${restore.output}`.trim()
|
|
1993
|
+
};
|
|
1994
|
+
}
|
|
1995
|
+
__name(stashPull, "stashPull");
|
|
1996
|
+
async function forcePull(cwd, env) {
|
|
1997
|
+
await refuseOperationInProgress(cwd, env);
|
|
1998
|
+
const toplevel = (await runGit2(cwd, ["rev-parse", "--show-toplevel"], env)).trim();
|
|
1999
|
+
if (await fs3.promises.realpath(cwd) !== await fs3.promises.realpath(toplevel)) {
|
|
2000
|
+
throw new GitPullFailure(
|
|
2001
|
+
"force_unsupported",
|
|
2002
|
+
"cannot discard changes: the workspace is a subdirectory of its repository, so discarding would also reset changes outside the workspace; resolve them from a terminal"
|
|
2003
|
+
);
|
|
2004
|
+
}
|
|
2005
|
+
await runGit2(cwd, ["fetch", "--prune"], env);
|
|
2006
|
+
const validated = await validatedUpstream(cwd, env);
|
|
2007
|
+
try {
|
|
2008
|
+
await runGit2(cwd, ["merge-base", "--is-ancestor", "HEAD", validated], env);
|
|
2009
|
+
} catch (err) {
|
|
2010
|
+
if (exitCode(err) === 1) {
|
|
2011
|
+
throw new GitPullFailure(
|
|
2012
|
+
"diverged",
|
|
2013
|
+
"cannot discard and update: the branch has diverged from its upstream; merge or rebase the local commits from a terminal first"
|
|
2014
|
+
);
|
|
2015
|
+
}
|
|
2016
|
+
throw err;
|
|
2017
|
+
}
|
|
2018
|
+
await refuseOperationInProgress(cwd, env);
|
|
2019
|
+
await runGit2(cwd, ["reset", "--hard"], env);
|
|
2020
|
+
await runGit2(cwd, ["clean", "-fd"], env);
|
|
2021
|
+
let output;
|
|
2022
|
+
try {
|
|
2023
|
+
output = await runGit2(cwd, ["merge", "--ff-only", validated], env);
|
|
2024
|
+
} catch (err) {
|
|
2025
|
+
throw new GitPullFailure(
|
|
2026
|
+
"pull_failed",
|
|
2027
|
+
`discard applied, but the update failed:
|
|
2028
|
+
${gitDetail(err)}`
|
|
2029
|
+
);
|
|
2030
|
+
}
|
|
2031
|
+
return { success: true, output: output.trim() };
|
|
2032
|
+
}
|
|
2033
|
+
__name(forcePull, "forcePull");
|
|
2034
|
+
async function gitPull(cwd, opts, env) {
|
|
2035
|
+
if (opts?.stash && opts?.force) {
|
|
2036
|
+
throw new Error("stash and force are mutually exclusive");
|
|
2037
|
+
}
|
|
2038
|
+
if (opts?.fetchOnly && (opts?.stash || opts?.force)) {
|
|
2039
|
+
throw new Error("fetchOnly cannot be combined with stash or force");
|
|
2040
|
+
}
|
|
2041
|
+
if (opts?.fetchOnly) {
|
|
2042
|
+
const output2 = await runGit2(cwd, ["fetch", "--all", "--prune"], env);
|
|
2043
|
+
return { success: true, output: output2.trim() };
|
|
2044
|
+
}
|
|
2045
|
+
if (opts?.force) return forcePull(cwd, env);
|
|
2046
|
+
if (opts?.stash) return stashPull(cwd, opts, env);
|
|
2047
|
+
const output = await runGit2(cwd, pullArgs(opts), env);
|
|
2048
|
+
return { success: true, output: output.trim() };
|
|
2049
|
+
}
|
|
2050
|
+
__name(gitPull, "gitPull");
|
|
2051
|
+
async function gitCommit(cwd, message, opts, env) {
|
|
2052
|
+
let savedIndex = null;
|
|
2053
|
+
if (opts?.all) {
|
|
2054
|
+
const tree = (await runGit2(cwd, ["write-tree"], env).catch(() => "")).trim();
|
|
2055
|
+
if (tree) {
|
|
2056
|
+
savedIndex = tree;
|
|
2057
|
+
} else {
|
|
2058
|
+
const unmerged = (await runGit2(cwd, ["ls-files", "--unmerged"], env)).trim();
|
|
2059
|
+
if (unmerged) {
|
|
2060
|
+
throw new Error(
|
|
2061
|
+
"cannot stage all changes: unresolved merge conflicts in the index"
|
|
2062
|
+
);
|
|
2063
|
+
}
|
|
2064
|
+
throw new Error(
|
|
2065
|
+
"cannot stage all changes: failed to snapshot index (write-tree failed)"
|
|
2066
|
+
);
|
|
2067
|
+
}
|
|
2068
|
+
}
|
|
2069
|
+
try {
|
|
2070
|
+
if (opts?.all) {
|
|
2071
|
+
await runGit2(cwd, ["add", "-A"], env);
|
|
2072
|
+
}
|
|
2073
|
+
await runGit2(cwd, ["commit", "-m", message], env);
|
|
2074
|
+
} catch (err) {
|
|
2075
|
+
if (savedIndex) {
|
|
2076
|
+
await runGit2(cwd, ["read-tree", savedIndex], env).catch((rollbackErr) => {
|
|
2077
|
+
console.error("git index rollback failed:", rollbackErr);
|
|
2078
|
+
});
|
|
2079
|
+
}
|
|
2080
|
+
throw err;
|
|
2081
|
+
}
|
|
2082
|
+
const sha = (await runGit2(cwd, ["rev-parse", "--short", "HEAD"], env)).trim();
|
|
2083
|
+
const subject = (await runGit2(cwd, ["log", "-1", "--format=%s"], env)).trim();
|
|
2084
|
+
return { sha, subject };
|
|
2085
|
+
}
|
|
2086
|
+
__name(gitCommit, "gitCommit");
|
|
129
2087
|
|
|
130
2088
|
// packages/core/src/utils/load-simple-git.ts
|
|
131
2089
|
init_esbuild_shims();
|
|
@@ -173,8 +2131,8 @@ async function initRepositoryWithMainBranch(git) {
|
|
|
173
2131
|
__name(initRepositoryWithMainBranch, "initRepositoryWithMainBranch");
|
|
174
2132
|
|
|
175
2133
|
// packages/core/src/services/gitWorktreeService.ts
|
|
176
|
-
var
|
|
177
|
-
var
|
|
2134
|
+
var execFileAsync3 = promisify3(execFile3);
|
|
2135
|
+
var debugLogger2 = createDebugLogger("GIT_WORKTREE_SERVICE");
|
|
178
2136
|
var WORKTREE_BRANCH_PREFIX = "worktree-";
|
|
179
2137
|
function worktreeBranchForSlug(slug) {
|
|
180
2138
|
return `${WORKTREE_BRANCH_PREFIX}${slug}`;
|
|
@@ -182,38 +2140,85 @@ function worktreeBranchForSlug(slug) {
|
|
|
182
2140
|
__name(worktreeBranchForSlug, "worktreeBranchForSlug");
|
|
183
2141
|
var WORKTREE_SESSION_FILE = ".o1-code-session";
|
|
184
2142
|
var WORKTREE_SESSION_MARKER_MAX_BYTES = 512;
|
|
2143
|
+
var DISPOSABLE_IGNORED_DIRS = /* @__PURE__ */ new Set(["node_modules", "dist", "coverage"]);
|
|
2144
|
+
async function worktreeHasWork(worktreePath) {
|
|
2145
|
+
try {
|
|
2146
|
+
await fs4.access(path5.join(worktreePath, ".git"));
|
|
2147
|
+
const { stdout } = await execFileAsync3(
|
|
2148
|
+
"git",
|
|
2149
|
+
[
|
|
2150
|
+
...NO_EXEC_CONFIG,
|
|
2151
|
+
"--no-optional-locks",
|
|
2152
|
+
"status",
|
|
2153
|
+
"--porcelain",
|
|
2154
|
+
"-z",
|
|
2155
|
+
"--untracked-files=normal",
|
|
2156
|
+
"--ignored=matching"
|
|
2157
|
+
],
|
|
2158
|
+
{
|
|
2159
|
+
cwd: worktreePath,
|
|
2160
|
+
env: gitEnv(),
|
|
2161
|
+
encoding: "utf8",
|
|
2162
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
2163
|
+
timeout: 3e4
|
|
2164
|
+
}
|
|
2165
|
+
);
|
|
2166
|
+
for (const record of stdout.split("\0")) {
|
|
2167
|
+
if (record.length === 0) continue;
|
|
2168
|
+
const status = record.slice(0, 2);
|
|
2169
|
+
const entry = record.slice(3);
|
|
2170
|
+
if (status !== "??" && status !== "!!") return true;
|
|
2171
|
+
if (entry === WORKTREE_SESSION_FILE) continue;
|
|
2172
|
+
if (status === "!!" && entry.split("/").some((segment) => DISPOSABLE_IGNORED_DIRS.has(segment))) {
|
|
2173
|
+
continue;
|
|
2174
|
+
}
|
|
2175
|
+
const stats = await fs4.lstat(
|
|
2176
|
+
path5.join(worktreePath, entry.replace(/\/$/, ""))
|
|
2177
|
+
);
|
|
2178
|
+
if (stats.isSymbolicLink()) continue;
|
|
2179
|
+
return true;
|
|
2180
|
+
}
|
|
2181
|
+
return false;
|
|
2182
|
+
} catch (error) {
|
|
2183
|
+
debugLogger2.warn(
|
|
2184
|
+
`worktreeHasWork: cannot inspect ${worktreePath}, keeping it: ${error}`
|
|
2185
|
+
);
|
|
2186
|
+
return true;
|
|
2187
|
+
}
|
|
2188
|
+
}
|
|
2189
|
+
__name(worktreeHasWork, "worktreeHasWork");
|
|
185
2190
|
var NO_EXEC_DIFF_FLAGS = ["--no-ext-diff", "--no-textconv"];
|
|
186
2191
|
async function addWorktreeSessionMarkerExclude(worktreePath) {
|
|
187
2192
|
try {
|
|
188
2193
|
const { simpleGit } = await loadSimpleGit();
|
|
189
2194
|
const wtGit = simpleGit(worktreePath);
|
|
190
2195
|
const commonDir = (await wtGit.revparse(["--git-common-dir"])).trim();
|
|
191
|
-
const excludePath =
|
|
192
|
-
await
|
|
2196
|
+
const excludePath = path5.isAbsolute(commonDir) ? path5.join(commonDir, "info", "exclude") : path5.join(worktreePath, commonDir, "info", "exclude");
|
|
2197
|
+
await fs4.mkdir(path5.dirname(excludePath), { recursive: true });
|
|
193
2198
|
let existing = "";
|
|
194
2199
|
try {
|
|
195
|
-
existing = await
|
|
2200
|
+
existing = await fs4.readFile(excludePath, "utf8");
|
|
196
2201
|
} catch {
|
|
197
2202
|
}
|
|
198
2203
|
const rules = [WORKTREE_SESSION_FILE, `${WORKTREE_SESSION_FILE}.*.tmp`];
|
|
199
2204
|
let next = existing;
|
|
200
2205
|
for (const rule of rules) {
|
|
201
2206
|
if (!next.split(/\r?\n/).includes(rule)) {
|
|
202
|
-
const
|
|
203
|
-
next = `${next}${
|
|
2207
|
+
const sep2 = next.length === 0 || next.endsWith("\n") ? "" : "\n";
|
|
2208
|
+
next = `${next}${sep2}${rule}
|
|
204
2209
|
`;
|
|
205
2210
|
}
|
|
206
2211
|
}
|
|
207
2212
|
if (next !== existing) {
|
|
208
|
-
await
|
|
2213
|
+
await fs4.writeFile(excludePath, next, "utf8");
|
|
209
2214
|
}
|
|
210
2215
|
} catch {
|
|
211
2216
|
}
|
|
212
2217
|
}
|
|
213
2218
|
__name(addWorktreeSessionMarkerExclude, "addWorktreeSessionMarkerExclude");
|
|
214
2219
|
async function writeWorktreeSessionMarker(worktreePath, sessionId) {
|
|
215
|
-
await
|
|
216
|
-
|
|
2220
|
+
await fs4.writeFile(
|
|
2221
|
+
path5.join(worktreePath, WORKTREE_SESSION_FILE),
|
|
217
2222
|
sessionId,
|
|
218
2223
|
"utf8"
|
|
219
2224
|
);
|
|
@@ -236,10 +2241,10 @@ var WorktreeMarkerCommittedError = class extends Error {
|
|
|
236
2241
|
};
|
|
237
2242
|
async function createWorktreeSessionMarkerExclusive(worktreePath, sessionId) {
|
|
238
2243
|
assertValidWorktreeSessionMarkerOwner(sessionId);
|
|
239
|
-
const markerPath =
|
|
2244
|
+
const markerPath = path5.join(worktreePath, WORKTREE_SESSION_FILE);
|
|
240
2245
|
const stagedPath = `${markerPath}.${randomBytes(6).toString("hex")}.tmp`;
|
|
241
2246
|
const flags = nodeFs.constants.O_WRONLY | nodeFs.constants.O_CREAT | nodeFs.constants.O_EXCL | (nodeFs.constants.O_NOFOLLOW ?? 0);
|
|
242
|
-
const handle = await
|
|
2247
|
+
const handle = await fs4.open(stagedPath, flags, 384);
|
|
243
2248
|
let stagedDev = 0;
|
|
244
2249
|
let stagedIno = 0;
|
|
245
2250
|
try {
|
|
@@ -253,20 +2258,20 @@ async function createWorktreeSessionMarkerExclusive(worktreePath, sessionId) {
|
|
|
253
2258
|
await handle.sync();
|
|
254
2259
|
const [after, pathStats] = await Promise.all([
|
|
255
2260
|
handle.stat(),
|
|
256
|
-
|
|
2261
|
+
fs4.lstat(stagedPath)
|
|
257
2262
|
]);
|
|
258
2263
|
if (!after.isFile() || after.nlink !== 1 || after.size > WORKTREE_SESSION_MARKER_MAX_BYTES || after.dev !== before.dev || after.ino !== before.ino || pathStats.dev !== after.dev || pathStats.ino !== after.ino) {
|
|
259
2264
|
throw new Error("Worktree session marker identity changed");
|
|
260
2265
|
}
|
|
261
|
-
await
|
|
2266
|
+
await fs4.link(stagedPath, markerPath);
|
|
262
2267
|
} catch (error) {
|
|
263
2268
|
await handle.close().catch(() => {
|
|
264
2269
|
});
|
|
265
2270
|
if (stagedIno !== 0) {
|
|
266
2271
|
try {
|
|
267
|
-
const current = await
|
|
2272
|
+
const current = await fs4.lstat(stagedPath);
|
|
268
2273
|
if (current.dev === stagedDev && current.ino === stagedIno) {
|
|
269
|
-
await
|
|
2274
|
+
await fs4.unlink(stagedPath);
|
|
270
2275
|
}
|
|
271
2276
|
} catch {
|
|
272
2277
|
}
|
|
@@ -274,7 +2279,7 @@ async function createWorktreeSessionMarkerExclusive(worktreePath, sessionId) {
|
|
|
274
2279
|
throw error;
|
|
275
2280
|
}
|
|
276
2281
|
try {
|
|
277
|
-
await
|
|
2282
|
+
await fs4.unlink(stagedPath);
|
|
278
2283
|
await handle.close();
|
|
279
2284
|
} catch (error) {
|
|
280
2285
|
await handle.close().catch(() => {
|
|
@@ -291,12 +2296,12 @@ function assertValidWorktreeSessionMarkerOwner(sessionId) {
|
|
|
291
2296
|
}
|
|
292
2297
|
__name(assertValidWorktreeSessionMarkerOwner, "assertValidWorktreeSessionMarkerOwner");
|
|
293
2298
|
async function readWorktreeSessionMarkerStrict(worktreePath) {
|
|
294
|
-
const markerPath =
|
|
2299
|
+
const markerPath = path5.join(worktreePath, WORKTREE_SESSION_FILE);
|
|
295
2300
|
let handle;
|
|
296
2301
|
let observedMarker = false;
|
|
297
2302
|
try {
|
|
298
2303
|
const flags = nodeFs.constants.O_RDONLY | (nodeFs.constants.O_NOFOLLOW ?? 0) | (nodeFs.constants.O_NONBLOCK ?? 0);
|
|
299
|
-
const before = await
|
|
2304
|
+
const before = await fs4.lstat(markerPath);
|
|
300
2305
|
observedMarker = true;
|
|
301
2306
|
if (before.isSymbolicLink() || !before.isFile() || before.nlink !== 1) {
|
|
302
2307
|
return { state: "invalid", reason: "unsafe marker file type" };
|
|
@@ -304,7 +2309,7 @@ async function readWorktreeSessionMarkerStrict(worktreePath) {
|
|
|
304
2309
|
if (before.ino === 0 || before.size > WORKTREE_SESSION_MARKER_MAX_BYTES) {
|
|
305
2310
|
return { state: "invalid", reason: "unsafe marker size or identity" };
|
|
306
2311
|
}
|
|
307
|
-
handle = await
|
|
2312
|
+
handle = await fs4.open(markerPath, flags);
|
|
308
2313
|
const opened = await handle.stat();
|
|
309
2314
|
if (!opened.isFile() || opened.nlink !== 1 || opened.dev !== before.dev || opened.ino !== before.ino) {
|
|
310
2315
|
return {
|
|
@@ -329,7 +2334,7 @@ async function readWorktreeSessionMarkerStrict(worktreePath) {
|
|
|
329
2334
|
}
|
|
330
2335
|
const raw = markerBuffer.subarray(0, bytesRead).toString("utf8");
|
|
331
2336
|
const after = await handle.stat();
|
|
332
|
-
const pathStats = await
|
|
2337
|
+
const pathStats = await fs4.lstat(markerPath);
|
|
333
2338
|
if (!after.isFile() || after.nlink !== 1 || after.size !== bytesRead || after.size > WORKTREE_SESSION_MARKER_MAX_BYTES || after.dev !== opened.dev || after.ino !== opened.ino || !pathStats.isFile() || pathStats.nlink !== 1 || pathStats.dev !== after.dev || pathStats.ino !== after.ino) {
|
|
334
2339
|
return {
|
|
335
2340
|
state: "invalid",
|
|
@@ -362,7 +2367,7 @@ async function readWorktreeSessionMarkerStrict(worktreePath) {
|
|
|
362
2367
|
}
|
|
363
2368
|
__name(readWorktreeSessionMarkerStrict, "readWorktreeSessionMarkerStrict");
|
|
364
2369
|
function readWorktreeSessionMarkerStrictSync(worktreePath) {
|
|
365
|
-
const markerPath =
|
|
2370
|
+
const markerPath = path5.join(worktreePath, WORKTREE_SESSION_FILE);
|
|
366
2371
|
let fd;
|
|
367
2372
|
let observedMarker = false;
|
|
368
2373
|
try {
|
|
@@ -463,7 +2468,7 @@ async function transferWorktreeSessionMarkerOwner(worktreePath, expectedOwner, n
|
|
|
463
2468
|
if (euid !== void 0 && opening.uid !== null && opening.uid !== euid) {
|
|
464
2469
|
throw new Error("Worktree marker is owned by a different uid");
|
|
465
2470
|
}
|
|
466
|
-
const markerPath =
|
|
2471
|
+
const markerPath = path5.join(worktreePath, WORKTREE_SESSION_FILE);
|
|
467
2472
|
await atomicWriteFile(markerPath, newOwner, {
|
|
468
2473
|
mode: 384,
|
|
469
2474
|
noFollow: true,
|
|
@@ -478,14 +2483,14 @@ async function transferWorktreeSessionMarkerOwner(worktreePath, expectedOwner, n
|
|
|
478
2483
|
}
|
|
479
2484
|
__name(transferWorktreeSessionMarkerOwner, "transferWorktreeSessionMarkerOwner");
|
|
480
2485
|
async function readWorktreeSessionMarker(worktreePath) {
|
|
481
|
-
const markerPath =
|
|
2486
|
+
const markerPath = path5.join(worktreePath, WORKTREE_SESSION_FILE);
|
|
482
2487
|
try {
|
|
483
|
-
const raw = await
|
|
2488
|
+
const raw = await fs4.readFile(markerPath, "utf8");
|
|
484
2489
|
const trimmed = raw.trim();
|
|
485
2490
|
return trimmed.length > 0 ? trimmed : null;
|
|
486
2491
|
} catch (error) {
|
|
487
2492
|
if (error.code !== "ENOENT") {
|
|
488
|
-
|
|
2493
|
+
debugLogger2.warn(
|
|
489
2494
|
`readWorktreeSessionMarker: cannot read ${markerPath}: ${error}`
|
|
490
2495
|
);
|
|
491
2496
|
}
|
|
@@ -517,7 +2522,7 @@ var GitWorktreeService = class _GitWorktreeService {
|
|
|
517
2522
|
gitPromise;
|
|
518
2523
|
customBaseDir;
|
|
519
2524
|
constructor(sourceRepoPath, customBaseDir) {
|
|
520
|
-
this.sourceRepoPath =
|
|
2525
|
+
this.sourceRepoPath = path5.resolve(sourceRepoPath);
|
|
521
2526
|
this.customBaseDir = customBaseDir;
|
|
522
2527
|
}
|
|
523
2528
|
getGit() {
|
|
@@ -532,23 +2537,23 @@ var GitWorktreeService = class _GitWorktreeService {
|
|
|
532
2537
|
*/
|
|
533
2538
|
static getBaseDir(customDir) {
|
|
534
2539
|
if (customDir) {
|
|
535
|
-
return
|
|
2540
|
+
return path5.resolve(customDir);
|
|
536
2541
|
}
|
|
537
|
-
return
|
|
2542
|
+
return path5.join(Storage.getGlobalO1CodeDir(), WORKTREES_DIR);
|
|
538
2543
|
}
|
|
539
2544
|
/**
|
|
540
2545
|
* Gets the directory for a specific session.
|
|
541
2546
|
* @param customBaseDir - Optional custom base directory override
|
|
542
2547
|
*/
|
|
543
2548
|
static getSessionDir(sessionId, customBaseDir) {
|
|
544
|
-
return
|
|
2549
|
+
return path5.join(_GitWorktreeService.getBaseDir(customBaseDir), sessionId);
|
|
545
2550
|
}
|
|
546
2551
|
/**
|
|
547
2552
|
* Gets the worktrees directory for a specific session.
|
|
548
2553
|
* @param customBaseDir - Optional custom base directory override
|
|
549
2554
|
*/
|
|
550
2555
|
static getWorktreesDir(sessionId, customBaseDir) {
|
|
551
|
-
return
|
|
2556
|
+
return path5.join(
|
|
552
2557
|
_GitWorktreeService.getSessionDir(sessionId, customBaseDir),
|
|
553
2558
|
WORKTREES_DIR
|
|
554
2559
|
);
|
|
@@ -589,7 +2594,7 @@ var GitWorktreeService = class _GitWorktreeService {
|
|
|
589
2594
|
const top = out.replace(/\n$/, "");
|
|
590
2595
|
return top.length > 0 ? top : null;
|
|
591
2596
|
} catch (error) {
|
|
592
|
-
|
|
2597
|
+
debugLogger2.warn(
|
|
593
2598
|
`getRepoTopLevel failed at ${this.sourceRepoPath}: ${error}`
|
|
594
2599
|
);
|
|
595
2600
|
return null;
|
|
@@ -634,7 +2639,7 @@ var GitWorktreeService = class _GitWorktreeService {
|
|
|
634
2639
|
if (mainPath.length === 0) return null;
|
|
635
2640
|
const realpathOr = /* @__PURE__ */ __name(async (p) => {
|
|
636
2641
|
try {
|
|
637
|
-
return await
|
|
2642
|
+
return await fs4.realpath(p);
|
|
638
2643
|
} catch {
|
|
639
2644
|
return p;
|
|
640
2645
|
}
|
|
@@ -645,14 +2650,14 @@ var GitWorktreeService = class _GitWorktreeService {
|
|
|
645
2650
|
simpleGit(mainPath).raw(["rev-parse", "--git-common-dir"])
|
|
646
2651
|
]);
|
|
647
2652
|
const ourCommonDir = await realpathOr(
|
|
648
|
-
|
|
2653
|
+
path5.resolve(this.sourceRepoPath, ourRaw.trim())
|
|
649
2654
|
);
|
|
650
2655
|
const anchorCommonDir = await realpathOr(
|
|
651
|
-
|
|
2656
|
+
path5.resolve(mainPath, anchorRaw.trim())
|
|
652
2657
|
);
|
|
653
2658
|
return ourCommonDir === anchorCommonDir ? mainPath : null;
|
|
654
2659
|
} catch (error) {
|
|
655
|
-
|
|
2660
|
+
debugLogger2.warn(
|
|
656
2661
|
`getMainWorktreePath failed at ${this.sourceRepoPath}: ${error}`
|
|
657
2662
|
);
|
|
658
2663
|
return null;
|
|
@@ -749,9 +2754,9 @@ var GitWorktreeService = class _GitWorktreeService {
|
|
|
749
2754
|
sessionId,
|
|
750
2755
|
this.customBaseDir
|
|
751
2756
|
);
|
|
752
|
-
await
|
|
2757
|
+
await fs4.mkdir(worktreesDir, { recursive: true });
|
|
753
2758
|
const sanitizedName = this.sanitizeName(name);
|
|
754
|
-
const worktreePath =
|
|
2759
|
+
const worktreePath = path5.join(worktreesDir, sanitizedName);
|
|
755
2760
|
const exists = await this.pathExists(worktreePath);
|
|
756
2761
|
if (exists) {
|
|
757
2762
|
return {
|
|
@@ -831,8 +2836,8 @@ var GitWorktreeService = class _GitWorktreeService {
|
|
|
831
2836
|
config.sessionId,
|
|
832
2837
|
this.customBaseDir
|
|
833
2838
|
);
|
|
834
|
-
await
|
|
835
|
-
const configPath =
|
|
2839
|
+
await fs4.mkdir(sessionDir, { recursive: true });
|
|
2840
|
+
const configPath = path5.join(sessionDir, "config.json");
|
|
836
2841
|
const configFile = {
|
|
837
2842
|
sessionId: config.sessionId,
|
|
838
2843
|
sourceRepoPath: config.sourceRepoPath,
|
|
@@ -841,7 +2846,7 @@ var GitWorktreeService = class _GitWorktreeService {
|
|
|
841
2846
|
createdAt: Date.now(),
|
|
842
2847
|
...config.metadata
|
|
843
2848
|
};
|
|
844
|
-
await
|
|
2849
|
+
await fs4.writeFile(configPath, JSON.stringify(configFile, null, 2));
|
|
845
2850
|
let dirtyStateSnapshot = "";
|
|
846
2851
|
try {
|
|
847
2852
|
dirtyStateSnapshot = (await (await this.getGit()).stash(["create"])).trim();
|
|
@@ -894,10 +2899,10 @@ var GitWorktreeService = class _GitWorktreeService {
|
|
|
894
2899
|
}
|
|
895
2900
|
for (const relPath of untrackedFiles) {
|
|
896
2901
|
try {
|
|
897
|
-
const src =
|
|
898
|
-
const dst =
|
|
899
|
-
await
|
|
900
|
-
await
|
|
2902
|
+
const src = path5.join(this.sourceRepoPath, relPath);
|
|
2903
|
+
const dst = path5.join(worktree.path, relPath);
|
|
2904
|
+
await fs4.mkdir(path5.dirname(dst), { recursive: true });
|
|
2905
|
+
await fs4.copyFile(src, dst);
|
|
901
2906
|
} catch {
|
|
902
2907
|
}
|
|
903
2908
|
}
|
|
@@ -922,11 +2927,11 @@ var GitWorktreeService = class _GitWorktreeService {
|
|
|
922
2927
|
this.customBaseDir
|
|
923
2928
|
);
|
|
924
2929
|
try {
|
|
925
|
-
const entries = await
|
|
2930
|
+
const entries = await fs4.readdir(worktreesDir, { withFileTypes: true });
|
|
926
2931
|
const worktrees = [];
|
|
927
2932
|
for (const entry of entries) {
|
|
928
2933
|
if (entry.isDirectory()) {
|
|
929
|
-
const worktreePath =
|
|
2934
|
+
const worktreePath = path5.join(worktreesDir, entry.name);
|
|
930
2935
|
let branchName = "";
|
|
931
2936
|
try {
|
|
932
2937
|
branchName = execSync("git rev-parse --abbrev-ref HEAD", {
|
|
@@ -938,7 +2943,7 @@ var GitWorktreeService = class _GitWorktreeService {
|
|
|
938
2943
|
}
|
|
939
2944
|
let createdAt = Date.now();
|
|
940
2945
|
try {
|
|
941
|
-
const stats = await
|
|
2946
|
+
const stats = await fs4.stat(worktreePath);
|
|
942
2947
|
createdAt = stats.birthtimeMs;
|
|
943
2948
|
} catch {
|
|
944
2949
|
}
|
|
@@ -978,7 +2983,7 @@ var GitWorktreeService = class _GitWorktreeService {
|
|
|
978
2983
|
return { success: true };
|
|
979
2984
|
} catch (error) {
|
|
980
2985
|
try {
|
|
981
|
-
await
|
|
2986
|
+
await fs4.rm(worktreePath, { recursive: true, force: true });
|
|
982
2987
|
await git.raw(["worktree", "prune"]);
|
|
983
2988
|
return { success: true };
|
|
984
2989
|
} catch (_rmError) {
|
|
@@ -1019,7 +3024,7 @@ var GitWorktreeService = class _GitWorktreeService {
|
|
|
1019
3024
|
this.customBaseDir
|
|
1020
3025
|
);
|
|
1021
3026
|
try {
|
|
1022
|
-
await
|
|
3027
|
+
await fs4.rm(sessionDir, { recursive: true, force: true });
|
|
1023
3028
|
} catch (error) {
|
|
1024
3029
|
result.errors.push(
|
|
1025
3030
|
`Failed to remove session directory: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
@@ -1084,17 +3089,17 @@ var GitWorktreeService = class _GitWorktreeService {
|
|
|
1084
3089
|
if (!patch.trim()) {
|
|
1085
3090
|
return { success: true };
|
|
1086
3091
|
}
|
|
1087
|
-
const patchFile =
|
|
3092
|
+
const patchFile = path5.join(
|
|
1088
3093
|
this.getBaseDirForInstance(),
|
|
1089
3094
|
`.worktree-apply-${Date.now()}-${Math.random().toString(16).slice(2)}.patch`
|
|
1090
3095
|
);
|
|
1091
|
-
await
|
|
1092
|
-
await
|
|
3096
|
+
await fs4.mkdir(path5.dirname(patchFile), { recursive: true });
|
|
3097
|
+
await fs4.writeFile(patchFile, patch, "utf-8");
|
|
1093
3098
|
try {
|
|
1094
3099
|
const applyArgs = hasBaseline ? ["apply", "--whitespace=nowarn", patchFile] : ["apply", "--3way", "--whitespace=nowarn", patchFile];
|
|
1095
3100
|
await targetGit.raw(applyArgs);
|
|
1096
3101
|
} finally {
|
|
1097
|
-
await
|
|
3102
|
+
await fs4.rm(patchFile, { force: true });
|
|
1098
3103
|
}
|
|
1099
3104
|
return { success: true };
|
|
1100
3105
|
} catch (error) {
|
|
@@ -1111,17 +3116,17 @@ var GitWorktreeService = class _GitWorktreeService {
|
|
|
1111
3116
|
const baseDir = _GitWorktreeService.getBaseDir(customBaseDir);
|
|
1112
3117
|
const sessions = [];
|
|
1113
3118
|
try {
|
|
1114
|
-
const entries = await
|
|
3119
|
+
const entries = await fs4.readdir(baseDir, { withFileTypes: true });
|
|
1115
3120
|
for (const entry of entries) {
|
|
1116
3121
|
if (entry.isDirectory()) {
|
|
1117
|
-
const configPath =
|
|
3122
|
+
const configPath = path5.join(baseDir, entry.name, "config.json");
|
|
1118
3123
|
try {
|
|
1119
|
-
const configContent = await
|
|
3124
|
+
const configContent = await fs4.readFile(configPath, "utf-8");
|
|
1120
3125
|
const config = JSON.parse(configContent);
|
|
1121
|
-
const worktreesDir =
|
|
3126
|
+
const worktreesDir = path5.join(baseDir, entry.name, WORKTREES_DIR);
|
|
1122
3127
|
let worktreeCount = 0;
|
|
1123
3128
|
try {
|
|
1124
|
-
const worktreeEntries = await
|
|
3129
|
+
const worktreeEntries = await fs4.readdir(worktreesDir);
|
|
1125
3130
|
worktreeCount = worktreeEntries.length;
|
|
1126
3131
|
} catch {
|
|
1127
3132
|
}
|
|
@@ -1175,7 +3180,7 @@ var GitWorktreeService = class _GitWorktreeService {
|
|
|
1175
3180
|
}
|
|
1176
3181
|
async pathExists(p) {
|
|
1177
3182
|
try {
|
|
1178
|
-
await
|
|
3183
|
+
await fs4.access(p);
|
|
1179
3184
|
return true;
|
|
1180
3185
|
} catch {
|
|
1181
3186
|
return false;
|
|
@@ -1192,13 +3197,13 @@ var GitWorktreeService = class _GitWorktreeService {
|
|
|
1192
3197
|
* repo: `<projectRoot>/.o1-code/worktrees`.
|
|
1193
3198
|
*/
|
|
1194
3199
|
getUserWorktreesDir() {
|
|
1195
|
-
return
|
|
3200
|
+
return path5.join(this.sourceRepoPath, ".o1-code", WORKTREES_DIR);
|
|
1196
3201
|
}
|
|
1197
3202
|
/**
|
|
1198
3203
|
* Returns the absolute worktree path for a given slug.
|
|
1199
3204
|
*/
|
|
1200
3205
|
getUserWorktreePath(slug) {
|
|
1201
|
-
return
|
|
3206
|
+
return path5.join(this.getUserWorktreesDir(), slug);
|
|
1202
3207
|
}
|
|
1203
3208
|
/**
|
|
1204
3209
|
* Generates an auto-slug `{adj}-{noun}-{6hex}` for an unnamed worktree.
|
|
@@ -1295,9 +3300,9 @@ var GitWorktreeService = class _GitWorktreeService {
|
|
|
1295
3300
|
async getRegisteredWorktreeBranch(worktreePath) {
|
|
1296
3301
|
let resolvedWorktreePath;
|
|
1297
3302
|
try {
|
|
1298
|
-
const
|
|
1299
|
-
if (!
|
|
1300
|
-
resolvedWorktreePath = await
|
|
3303
|
+
const stat4 = await fs4.stat(worktreePath);
|
|
3304
|
+
if (!stat4.isDirectory()) return null;
|
|
3305
|
+
resolvedWorktreePath = await fs4.realpath(worktreePath);
|
|
1301
3306
|
} catch {
|
|
1302
3307
|
return null;
|
|
1303
3308
|
}
|
|
@@ -1320,27 +3325,27 @@ var GitWorktreeService = class _GitWorktreeService {
|
|
|
1320
3325
|
"--show-toplevel"
|
|
1321
3326
|
])
|
|
1322
3327
|
]);
|
|
1323
|
-
ourCommonDir =
|
|
3328
|
+
ourCommonDir = path5.resolve(this.sourceRepoPath, ourRaw.trim());
|
|
1324
3329
|
const lines = probeRaw.split("\n").map((l) => l.trim()).filter((l) => l.length > 0);
|
|
1325
3330
|
if (lines.length < 4) return null;
|
|
1326
3331
|
headCommit = lines[0];
|
|
1327
3332
|
branch = lines[1];
|
|
1328
|
-
probeCommonDir =
|
|
1329
|
-
probeToplevel =
|
|
3333
|
+
probeCommonDir = path5.resolve(worktreePath, lines[2]);
|
|
3334
|
+
probeToplevel = path5.resolve(lines[3]);
|
|
1330
3335
|
} catch (error) {
|
|
1331
|
-
|
|
3336
|
+
debugLogger2.debug(
|
|
1332
3337
|
`getRegisteredWorktreeBranch: probe at ${worktreePath} failed: ${error}`
|
|
1333
3338
|
);
|
|
1334
3339
|
return null;
|
|
1335
3340
|
}
|
|
1336
3341
|
if (probeCommonDir !== ourCommonDir) {
|
|
1337
|
-
|
|
3342
|
+
debugLogger2.debug(
|
|
1338
3343
|
`getRegisteredWorktreeBranch: ${worktreePath} belongs to a different repo (common-dir=${probeCommonDir}, expected ${ourCommonDir})`
|
|
1339
3344
|
);
|
|
1340
3345
|
return null;
|
|
1341
3346
|
}
|
|
1342
3347
|
if (probeToplevel !== resolvedWorktreePath) {
|
|
1343
|
-
|
|
3348
|
+
debugLogger2.debug(
|
|
1344
3349
|
`getRegisteredWorktreeBranch: ${worktreePath} is not a registered worktree (toplevel=${probeToplevel}, expected ${resolvedWorktreePath})`
|
|
1345
3350
|
);
|
|
1346
3351
|
return null;
|
|
@@ -1385,34 +3390,34 @@ var GitWorktreeService = class _GitWorktreeService {
|
|
|
1385
3390
|
async isRegisteredLinkedWorktree(worktreePath) {
|
|
1386
3391
|
const realpathOr = /* @__PURE__ */ __name(async (p) => {
|
|
1387
3392
|
try {
|
|
1388
|
-
return await
|
|
3393
|
+
return await fs4.realpath(p);
|
|
1389
3394
|
} catch {
|
|
1390
|
-
return
|
|
3395
|
+
return path5.resolve(p);
|
|
1391
3396
|
}
|
|
1392
3397
|
}, "realpathOr");
|
|
1393
3398
|
try {
|
|
1394
|
-
const target = await
|
|
1395
|
-
const ourCommonDir =
|
|
3399
|
+
const target = await fs4.realpath(worktreePath);
|
|
3400
|
+
const ourCommonDir = path5.resolve(
|
|
1396
3401
|
this.sourceRepoPath,
|
|
1397
3402
|
(await (await this.getGit()).raw(["rev-parse", "--git-common-dir"])).trim()
|
|
1398
3403
|
);
|
|
1399
|
-
const worktreesDir =
|
|
3404
|
+
const worktreesDir = path5.join(ourCommonDir, "worktrees");
|
|
1400
3405
|
let entryNames;
|
|
1401
3406
|
try {
|
|
1402
|
-
entryNames = await
|
|
3407
|
+
entryNames = await fs4.readdir(worktreesDir);
|
|
1403
3408
|
} catch {
|
|
1404
3409
|
return false;
|
|
1405
3410
|
}
|
|
1406
3411
|
let entryGitDir = null;
|
|
1407
3412
|
for (const name of entryNames) {
|
|
1408
|
-
const entry =
|
|
3413
|
+
const entry = path5.join(worktreesDir, name);
|
|
1409
3414
|
let pointer;
|
|
1410
3415
|
try {
|
|
1411
|
-
pointer = (await
|
|
3416
|
+
pointer = (await fs4.readFile(path5.join(entry, "gitdir"), "utf8")).trim();
|
|
1412
3417
|
} catch {
|
|
1413
3418
|
continue;
|
|
1414
3419
|
}
|
|
1415
|
-
if (await realpathOr(
|
|
3420
|
+
if (await realpathOr(path5.dirname(pointer)) === target) {
|
|
1416
3421
|
entryGitDir = entry;
|
|
1417
3422
|
break;
|
|
1418
3423
|
}
|
|
@@ -1420,10 +3425,10 @@ var GitWorktreeService = class _GitWorktreeService {
|
|
|
1420
3425
|
if (!entryGitDir) return false;
|
|
1421
3426
|
const { simpleGit } = await loadSimpleGit();
|
|
1422
3427
|
const rawGitDir = (await simpleGit(target).raw(["rev-parse", "--git-dir"])).trim();
|
|
1423
|
-
const probeGitDir = await realpathOr(
|
|
3428
|
+
const probeGitDir = await realpathOr(path5.resolve(target, rawGitDir));
|
|
1424
3429
|
return probeGitDir === await realpathOr(entryGitDir);
|
|
1425
3430
|
} catch (error) {
|
|
1426
|
-
|
|
3431
|
+
debugLogger2.debug(
|
|
1427
3432
|
`isRegisteredLinkedWorktree: probe at ${worktreePath} failed: ${error}`
|
|
1428
3433
|
);
|
|
1429
3434
|
return false;
|
|
@@ -1468,7 +3473,7 @@ var GitWorktreeService = class _GitWorktreeService {
|
|
|
1468
3473
|
}
|
|
1469
3474
|
const refspec = `pull/${prNumberStr}/head`;
|
|
1470
3475
|
try {
|
|
1471
|
-
await
|
|
3476
|
+
await execFileAsync3(
|
|
1472
3477
|
"git",
|
|
1473
3478
|
["fetch", "--end-of-options", "origin", refspec],
|
|
1474
3479
|
{
|
|
@@ -1502,7 +3507,7 @@ var GitWorktreeService = class _GitWorktreeService {
|
|
|
1502
3507
|
}
|
|
1503
3508
|
const firstLine = stderr.split("\n").find((l) => l.trim().length > 0);
|
|
1504
3509
|
const detail = firstLine ? ` (${firstLine.trim()})` : "";
|
|
1505
|
-
|
|
3510
|
+
debugLogger2.warn(
|
|
1506
3511
|
`fetchPullRequestRef: git fetch pull/${prNumber}/head failed: ${error}`
|
|
1507
3512
|
);
|
|
1508
3513
|
return {
|
|
@@ -1565,18 +3570,18 @@ var GitWorktreeService = class _GitWorktreeService {
|
|
|
1565
3570
|
options?.prBacked === true ? { allowPrBackedShape: true } : void 0
|
|
1566
3571
|
);
|
|
1567
3572
|
if (validationError) {
|
|
1568
|
-
|
|
3573
|
+
debugLogger2.warn(
|
|
1569
3574
|
`createUserWorktree: invalid slug ${slug}: ${validationError}`
|
|
1570
3575
|
);
|
|
1571
3576
|
return { success: false, error: validationError };
|
|
1572
3577
|
}
|
|
1573
3578
|
try {
|
|
1574
3579
|
const worktreesDir = this.getUserWorktreesDir();
|
|
1575
|
-
await
|
|
1576
|
-
const worktreePath =
|
|
3580
|
+
await fs4.mkdir(worktreesDir, { recursive: true });
|
|
3581
|
+
const worktreePath = path5.join(worktreesDir, slug);
|
|
1577
3582
|
if (await fileExists(worktreePath)) {
|
|
1578
3583
|
const error = `Worktree already exists at ${worktreePath}`;
|
|
1579
|
-
|
|
3584
|
+
debugLogger2.warn(`createUserWorktree: ${error}`);
|
|
1580
3585
|
return { success: false, error };
|
|
1581
3586
|
}
|
|
1582
3587
|
await this.ensureWorktreesGitignored();
|
|
@@ -1585,12 +3590,12 @@ var GitWorktreeService = class _GitWorktreeService {
|
|
|
1585
3590
|
const branchExists = await this.localBranchExists(branchName);
|
|
1586
3591
|
if (branchExists) {
|
|
1587
3592
|
const error = `Cannot create worktree "${slug}": branch ${branchName} already exists. Choose a different name, or delete the branch first (e.g. \`git branch -d ${branchName}\`).`;
|
|
1588
|
-
|
|
3593
|
+
debugLogger2.warn(`createUserWorktree: ${error}`);
|
|
1589
3594
|
return { success: false, error };
|
|
1590
3595
|
}
|
|
1591
3596
|
await (await this.getGit()).raw(["worktree", "add", "-b", branchName, worktreePath, base]);
|
|
1592
3597
|
await this.configureHooksPath(worktreePath).catch((error) => {
|
|
1593
|
-
|
|
3598
|
+
debugLogger2.warn(
|
|
1594
3599
|
`createUserWorktree: failed to configure core.hooksPath for ${slug}: ${error}`
|
|
1595
3600
|
);
|
|
1596
3601
|
});
|
|
@@ -1600,7 +3605,7 @@ var GitWorktreeService = class _GitWorktreeService {
|
|
|
1600
3605
|
worktreePath,
|
|
1601
3606
|
symlinkPaths
|
|
1602
3607
|
).catch((error) => {
|
|
1603
|
-
|
|
3608
|
+
debugLogger2.warn(
|
|
1604
3609
|
`createUserWorktree: symlinkConfiguredDirectories failed for ${slug}: ${error}`
|
|
1605
3610
|
);
|
|
1606
3611
|
});
|
|
@@ -1616,7 +3621,7 @@ var GitWorktreeService = class _GitWorktreeService {
|
|
|
1616
3621
|
return { success: true, worktree };
|
|
1617
3622
|
} catch (error) {
|
|
1618
3623
|
const message = `Failed to create worktree "${slug}": ${error instanceof Error ? error.message : "Unknown error"}`;
|
|
1619
|
-
|
|
3624
|
+
debugLogger2.warn(`createUserWorktree: ${message}`);
|
|
1620
3625
|
return { success: false, error: message };
|
|
1621
3626
|
}
|
|
1622
3627
|
}
|
|
@@ -1635,14 +3640,14 @@ var GitWorktreeService = class _GitWorktreeService {
|
|
|
1635
3640
|
* creation so the extra ~14ms isn't worth the file-parsing complexity).
|
|
1636
3641
|
*/
|
|
1637
3642
|
async configureHooksPath(worktreePath) {
|
|
1638
|
-
const huskyPath =
|
|
3643
|
+
const huskyPath = path5.join(this.sourceRepoPath, ".husky");
|
|
1639
3644
|
let hooksPath = null;
|
|
1640
3645
|
try {
|
|
1641
|
-
await
|
|
3646
|
+
await fs4.stat(huskyPath);
|
|
1642
3647
|
hooksPath = huskyPath;
|
|
1643
3648
|
} catch (error) {
|
|
1644
3649
|
if (!(isNodeError(error) && error.code === "ENOENT")) {
|
|
1645
|
-
|
|
3650
|
+
debugLogger2.warn(
|
|
1646
3651
|
`configureHooksPath: cannot stat ${huskyPath}: ${error}`
|
|
1647
3652
|
);
|
|
1648
3653
|
}
|
|
@@ -1650,13 +3655,13 @@ var GitWorktreeService = class _GitWorktreeService {
|
|
|
1650
3655
|
if (!hooksPath) {
|
|
1651
3656
|
try {
|
|
1652
3657
|
const commonDir = (await (await this.getGit()).raw(["rev-parse", "--git-common-dir"])).trim();
|
|
1653
|
-
const resolvedCommonDir =
|
|
1654
|
-
const candidate =
|
|
1655
|
-
await
|
|
3658
|
+
const resolvedCommonDir = path5.isAbsolute(commonDir) ? commonDir : path5.resolve(this.sourceRepoPath, commonDir);
|
|
3659
|
+
const candidate = path5.join(resolvedCommonDir, "hooks");
|
|
3660
|
+
await fs4.stat(candidate);
|
|
1656
3661
|
hooksPath = candidate;
|
|
1657
3662
|
} catch (error) {
|
|
1658
3663
|
if (!(isNodeError(error) && error.code === "ENOENT")) {
|
|
1659
|
-
|
|
3664
|
+
debugLogger2.warn(
|
|
1660
3665
|
`configureHooksPath: cannot resolve git common hooks dir: ${error}`
|
|
1661
3666
|
);
|
|
1662
3667
|
}
|
|
@@ -1675,7 +3680,7 @@ var GitWorktreeService = class _GitWorktreeService {
|
|
|
1675
3680
|
if (existing === "") {
|
|
1676
3681
|
await worktreeGit.raw(["config", "core.hooksPath", hooksPath]);
|
|
1677
3682
|
} else if (existing !== hooksPath) {
|
|
1678
|
-
|
|
3683
|
+
debugLogger2.debug(
|
|
1679
3684
|
`configureHooksPath: preserving existing core.hooksPath=${existing} (o1-code would have set it to ${hooksPath})`
|
|
1680
3685
|
);
|
|
1681
3686
|
}
|
|
@@ -1707,70 +3712,70 @@ var GitWorktreeService = class _GitWorktreeService {
|
|
|
1707
3712
|
async symlinkConfiguredDirectories(worktreePath, configured) {
|
|
1708
3713
|
let repoRootAbs;
|
|
1709
3714
|
try {
|
|
1710
|
-
repoRootAbs = await
|
|
3715
|
+
repoRootAbs = await fs4.realpath(this.sourceRepoPath);
|
|
1711
3716
|
} catch {
|
|
1712
|
-
|
|
3717
|
+
debugLogger2.warn(
|
|
1713
3718
|
`symlinkConfiguredDirectories: cannot realpath sourceRepoPath "${this.sourceRepoPath}", skipping all entries`
|
|
1714
3719
|
);
|
|
1715
3720
|
return;
|
|
1716
3721
|
}
|
|
1717
|
-
const gitDirAbs =
|
|
1718
|
-
const o1codeDirAbs =
|
|
1719
|
-
const realWorktreePath = await
|
|
3722
|
+
const gitDirAbs = path5.join(repoRootAbs, ".git");
|
|
3723
|
+
const o1codeDirAbs = path5.join(repoRootAbs, ".o1-code");
|
|
3724
|
+
const realWorktreePath = await fs4.realpath(worktreePath).catch(() => worktreePath);
|
|
1720
3725
|
for (const raw of configured) {
|
|
1721
3726
|
if (typeof raw !== "string" || raw.length === 0) {
|
|
1722
|
-
|
|
3727
|
+
debugLogger2.warn(
|
|
1723
3728
|
`symlinkConfiguredDirectories: skipping non-string / empty entry: ${JSON.stringify(raw)}`
|
|
1724
3729
|
);
|
|
1725
3730
|
continue;
|
|
1726
3731
|
}
|
|
1727
|
-
if (
|
|
1728
|
-
|
|
3732
|
+
if (path5.isAbsolute(raw)) {
|
|
3733
|
+
debugLogger2.warn(
|
|
1729
3734
|
`symlinkConfiguredDirectories: refusing absolute path "${raw}"`
|
|
1730
3735
|
);
|
|
1731
3736
|
continue;
|
|
1732
3737
|
}
|
|
1733
3738
|
if (raw.split(/[\\/]/).includes("..")) {
|
|
1734
|
-
|
|
3739
|
+
debugLogger2.warn(
|
|
1735
3740
|
`symlinkConfiguredDirectories: refusing path "${raw}" \u2014 contains '..' segment`
|
|
1736
3741
|
);
|
|
1737
3742
|
continue;
|
|
1738
3743
|
}
|
|
1739
|
-
const sourceAbs =
|
|
3744
|
+
const sourceAbs = path5.resolve(repoRootAbs, raw);
|
|
1740
3745
|
if (sourceAbs === repoRootAbs) {
|
|
1741
|
-
|
|
3746
|
+
debugLogger2.warn(
|
|
1742
3747
|
`symlinkConfiguredDirectories: refusing empty / repo-root path "${raw}"`
|
|
1743
3748
|
);
|
|
1744
3749
|
continue;
|
|
1745
3750
|
}
|
|
1746
3751
|
if (!isWithinRoot(sourceAbs, repoRootAbs)) {
|
|
1747
|
-
|
|
3752
|
+
debugLogger2.warn(
|
|
1748
3753
|
`symlinkConfiguredDirectories: refusing path "${raw}" \u2014 resolves outside repo root (${sourceAbs} vs ${repoRootAbs})`
|
|
1749
3754
|
);
|
|
1750
3755
|
continue;
|
|
1751
3756
|
}
|
|
1752
3757
|
if (isWithinRoot(sourceAbs, gitDirAbs)) {
|
|
1753
|
-
|
|
3758
|
+
debugLogger2.warn(
|
|
1754
3759
|
`symlinkConfiguredDirectories: refusing git-internal path "${raw}"`
|
|
1755
3760
|
);
|
|
1756
3761
|
continue;
|
|
1757
3762
|
}
|
|
1758
3763
|
if (isWithinRoot(sourceAbs, o1codeDirAbs)) {
|
|
1759
|
-
|
|
3764
|
+
debugLogger2.warn(
|
|
1760
3765
|
`symlinkConfiguredDirectories: refusing path "${raw}" \u2014 the .o1-code tree is CLI-managed; symlinking any of it could create a worktrees-inside-worktrees loop or alias CLI metadata.`
|
|
1761
3766
|
);
|
|
1762
3767
|
continue;
|
|
1763
3768
|
}
|
|
1764
3769
|
let sourceStat = null;
|
|
1765
3770
|
try {
|
|
1766
|
-
sourceStat = await
|
|
3771
|
+
sourceStat = await fs4.stat(sourceAbs);
|
|
1767
3772
|
} catch (error) {
|
|
1768
3773
|
if (isNodeError(error) && error.code === "ENOENT") {
|
|
1769
|
-
|
|
3774
|
+
debugLogger2.debug(
|
|
1770
3775
|
`symlinkConfiguredDirectories: source missing, skipping: ${sourceAbs}`
|
|
1771
3776
|
);
|
|
1772
3777
|
} else {
|
|
1773
|
-
|
|
3778
|
+
debugLogger2.warn(
|
|
1774
3779
|
`symlinkConfiguredDirectories: cannot stat ${sourceAbs}: ${error}`
|
|
1775
3780
|
);
|
|
1776
3781
|
}
|
|
@@ -1778,68 +3783,68 @@ var GitWorktreeService = class _GitWorktreeService {
|
|
|
1778
3783
|
}
|
|
1779
3784
|
let realSource;
|
|
1780
3785
|
try {
|
|
1781
|
-
realSource = await
|
|
3786
|
+
realSource = await fs4.realpath(sourceAbs);
|
|
1782
3787
|
} catch (error) {
|
|
1783
|
-
|
|
3788
|
+
debugLogger2.warn(
|
|
1784
3789
|
`symlinkConfiguredDirectories: cannot realpath source "${sourceAbs}": ${error}`
|
|
1785
3790
|
);
|
|
1786
3791
|
continue;
|
|
1787
3792
|
}
|
|
1788
3793
|
if (!isWithinRoot(realSource, repoRootAbs)) {
|
|
1789
|
-
|
|
3794
|
+
debugLogger2.warn(
|
|
1790
3795
|
`symlinkConfiguredDirectories: refusing path "${raw}" \u2014 real source ${realSource} escapes repo root ${repoRootAbs}`
|
|
1791
3796
|
);
|
|
1792
3797
|
continue;
|
|
1793
3798
|
}
|
|
1794
3799
|
if (isWithinRoot(realSource, gitDirAbs)) {
|
|
1795
|
-
|
|
3800
|
+
debugLogger2.warn(
|
|
1796
3801
|
`symlinkConfiguredDirectories: refusing path "${raw}" \u2014 real source ${realSource} resolves inside .git`
|
|
1797
3802
|
);
|
|
1798
3803
|
continue;
|
|
1799
3804
|
}
|
|
1800
3805
|
if (isWithinRoot(realSource, o1codeDirAbs)) {
|
|
1801
|
-
|
|
3806
|
+
debugLogger2.warn(
|
|
1802
3807
|
`symlinkConfiguredDirectories: refusing path "${raw}" \u2014 real source ${realSource} resolves inside .o1-code`
|
|
1803
3808
|
);
|
|
1804
3809
|
continue;
|
|
1805
3810
|
}
|
|
1806
|
-
const destAbs =
|
|
3811
|
+
const destAbs = path5.join(worktreePath, raw);
|
|
1807
3812
|
try {
|
|
1808
|
-
await
|
|
3813
|
+
await fs4.mkdir(path5.dirname(destAbs), { recursive: true });
|
|
1809
3814
|
} catch (error) {
|
|
1810
|
-
|
|
3815
|
+
debugLogger2.warn(
|
|
1811
3816
|
`symlinkConfiguredDirectories: cannot mkdir parent of ${destAbs}: ${error}`
|
|
1812
3817
|
);
|
|
1813
3818
|
continue;
|
|
1814
3819
|
}
|
|
1815
3820
|
let realDestParent;
|
|
1816
3821
|
try {
|
|
1817
|
-
realDestParent = await
|
|
3822
|
+
realDestParent = await fs4.realpath(path5.dirname(destAbs));
|
|
1818
3823
|
} catch (error) {
|
|
1819
|
-
|
|
1820
|
-
`symlinkConfiguredDirectories: cannot realpath dest parent for "${raw}" (${
|
|
3824
|
+
debugLogger2.warn(
|
|
3825
|
+
`symlinkConfiguredDirectories: cannot realpath dest parent for "${raw}" (${path5.dirname(destAbs)}): ${error}`
|
|
1821
3826
|
);
|
|
1822
3827
|
continue;
|
|
1823
3828
|
}
|
|
1824
3829
|
if (!isWithinRoot(realDestParent, realWorktreePath)) {
|
|
1825
|
-
|
|
3830
|
+
debugLogger2.warn(
|
|
1826
3831
|
`symlinkConfiguredDirectories: refusing path "${raw}" \u2014 dest parent ${realDestParent} escapes worktree root ${realWorktreePath} (committed-symlink chain)`
|
|
1827
3832
|
);
|
|
1828
3833
|
continue;
|
|
1829
3834
|
}
|
|
1830
3835
|
try {
|
|
1831
3836
|
const symlinkType = sourceStat.isDirectory() ? process.platform === "win32" ? "junction" : "dir" : "file";
|
|
1832
|
-
await
|
|
1833
|
-
|
|
3837
|
+
await fs4.symlink(realSource, destAbs, symlinkType);
|
|
3838
|
+
debugLogger2.debug(
|
|
1834
3839
|
`symlinkConfiguredDirectories: linked ${destAbs} \u2192 ${realSource} (${symlinkType})`
|
|
1835
3840
|
);
|
|
1836
3841
|
} catch (error) {
|
|
1837
3842
|
if (isNodeError(error) && error.code === "EEXIST") {
|
|
1838
|
-
|
|
3843
|
+
debugLogger2.debug(
|
|
1839
3844
|
`symlinkConfiguredDirectories: destination exists, skipping: ${destAbs}`
|
|
1840
3845
|
);
|
|
1841
3846
|
} else {
|
|
1842
|
-
|
|
3847
|
+
debugLogger2.warn(
|
|
1843
3848
|
`symlinkConfiguredDirectories: failed to link ${destAbs} \u2192 ${realSource}: ${error}`
|
|
1844
3849
|
);
|
|
1845
3850
|
}
|
|
@@ -1870,7 +3875,7 @@ var GitWorktreeService = class _GitWorktreeService {
|
|
|
1870
3875
|
]);
|
|
1871
3876
|
return out.trim().length > 0;
|
|
1872
3877
|
} catch (error) {
|
|
1873
|
-
|
|
3878
|
+
debugLogger2.warn(`localBranchExists failed for ${branchName}: ${error}`);
|
|
1874
3879
|
return false;
|
|
1875
3880
|
}
|
|
1876
3881
|
}
|
|
@@ -1882,11 +3887,11 @@ var GitWorktreeService = class _GitWorktreeService {
|
|
|
1882
3887
|
*/
|
|
1883
3888
|
async ensureWorktreesGitignored() {
|
|
1884
3889
|
try {
|
|
1885
|
-
const o1codeDir =
|
|
1886
|
-
await
|
|
1887
|
-
const gitignorePath =
|
|
3890
|
+
const o1codeDir = path5.join(this.sourceRepoPath, ".o1-code");
|
|
3891
|
+
await fs4.mkdir(o1codeDir, { recursive: true });
|
|
3892
|
+
const gitignorePath = path5.join(o1codeDir, ".gitignore");
|
|
1888
3893
|
try {
|
|
1889
|
-
await
|
|
3894
|
+
await fs4.writeFile(
|
|
1890
3895
|
gitignorePath,
|
|
1891
3896
|
`# Auto-generated by o1-code.
|
|
1892
3897
|
${WORKTREES_DIR}/
|
|
@@ -1900,7 +3905,7 @@ ${WORKTREES_DIR}/
|
|
|
1900
3905
|
throw error;
|
|
1901
3906
|
}
|
|
1902
3907
|
} catch (error) {
|
|
1903
|
-
|
|
3908
|
+
debugLogger2.warn(
|
|
1904
3909
|
`ensureWorktreesGitignored failed (non-fatal): ${error}`
|
|
1905
3910
|
);
|
|
1906
3911
|
}
|
|
@@ -1930,7 +3935,7 @@ ${WORKTREES_DIR}/
|
|
|
1930
3935
|
await git.branch(["-d", branchName]);
|
|
1931
3936
|
return { success: true };
|
|
1932
3937
|
} catch (error) {
|
|
1933
|
-
|
|
3938
|
+
debugLogger2.warn(
|
|
1934
3939
|
`removeUserWorktree: safe branch delete failed for ${branchName}: ${error}`
|
|
1935
3940
|
);
|
|
1936
3941
|
}
|
|
@@ -1939,7 +3944,7 @@ ${WORKTREES_DIR}/
|
|
|
1939
3944
|
await git.branch(["-D", branchName]);
|
|
1940
3945
|
return { success: true };
|
|
1941
3946
|
} catch (error) {
|
|
1942
|
-
|
|
3947
|
+
debugLogger2.warn(
|
|
1943
3948
|
`removeUserWorktree: force branch delete failed for ${branchName}: ${error}`
|
|
1944
3949
|
);
|
|
1945
3950
|
}
|
|
@@ -1973,7 +3978,7 @@ ${WORKTREES_DIR}/
|
|
|
1973
3978
|
])).split("\n").map((s) => s.trim()).filter((s) => s.length > 0 && s !== `refs/heads/${branchName}`);
|
|
1974
3979
|
return refs.length === 0;
|
|
1975
3980
|
} catch (error) {
|
|
1976
|
-
|
|
3981
|
+
debugLogger2.warn(
|
|
1977
3982
|
`hasUnmergedWorktreeCommits failed for slug ${slug}: ${error}`
|
|
1978
3983
|
);
|
|
1979
3984
|
return true;
|
|
@@ -1989,7 +3994,7 @@ ${WORKTREES_DIR}/
|
|
|
1989
3994
|
*/
|
|
1990
3995
|
async hasWorktreeChanges(worktreePath) {
|
|
1991
3996
|
try {
|
|
1992
|
-
const { stdout } = await
|
|
3997
|
+
const { stdout } = await execFileAsync3(
|
|
1993
3998
|
"git",
|
|
1994
3999
|
[
|
|
1995
4000
|
...NO_EXEC_CONFIG,
|
|
@@ -2015,7 +4020,7 @@ ${WORKTREES_DIR}/
|
|
|
2015
4020
|
*/
|
|
2016
4021
|
async countWorktreeChanges(worktreePath) {
|
|
2017
4022
|
try {
|
|
2018
|
-
const { stdout } = await
|
|
4023
|
+
const { stdout } = await execFileAsync3(
|
|
2019
4024
|
"git",
|
|
2020
4025
|
[
|
|
2021
4026
|
...NO_EXEC_CONFIG,
|
|
@@ -2048,12 +4053,44 @@ ${WORKTREES_DIR}/
|
|
|
2048
4053
|
};
|
|
2049
4054
|
|
|
2050
4055
|
export {
|
|
4056
|
+
MAX_FILES,
|
|
4057
|
+
MAX_DIFF_SIZE_BYTES,
|
|
4058
|
+
MAX_LINES_PER_FILE,
|
|
4059
|
+
MAX_FILES_FOR_DETAILS,
|
|
4060
|
+
fetchGitDiff,
|
|
4061
|
+
fetchGitDiffHunks,
|
|
4062
|
+
fetchGitDiffHunksForFile,
|
|
4063
|
+
parseGitNumstat,
|
|
4064
|
+
parseGitDiff,
|
|
4065
|
+
unquoteCStylePath,
|
|
4066
|
+
getWorkingTreeLineStats,
|
|
4067
|
+
parseDeletedFromNameStatus,
|
|
4068
|
+
getGitWorkingTreeStatus,
|
|
4069
|
+
parseStatusBranchLine,
|
|
4070
|
+
parseStatusEntries,
|
|
4071
|
+
MAX_LOG_LIMIT,
|
|
4072
|
+
DEFAULT_LOG_LIMIT,
|
|
4073
|
+
fetchGitLog,
|
|
4074
|
+
fetchGitCommitDetail,
|
|
2051
4075
|
writeRuntimeStatus,
|
|
2052
4076
|
readRuntimeStatus,
|
|
2053
4077
|
clearRuntimeStatus,
|
|
4078
|
+
isValidRefName,
|
|
4079
|
+
resolveBranchName,
|
|
4080
|
+
watchRepoBranch,
|
|
4081
|
+
gitEnv,
|
|
4082
|
+
runGit2 as runGit,
|
|
4083
|
+
fetchGitBranches,
|
|
4084
|
+
isValidCheckoutRef,
|
|
4085
|
+
gitCheckout,
|
|
4086
|
+
gitCreateBranch,
|
|
4087
|
+
gitPush,
|
|
4088
|
+
GitPullFailure,
|
|
4089
|
+
gitPull,
|
|
4090
|
+
gitCommit,
|
|
2054
4091
|
loadSimpleGit,
|
|
2055
4092
|
worktreeBranchForSlug,
|
|
2056
|
-
|
|
4093
|
+
worktreeHasWork,
|
|
2057
4094
|
writeWorktreeSessionMarker,
|
|
2058
4095
|
WorktreeMarkerCommittedError,
|
|
2059
4096
|
createWorktreeSessionMarkerExclusive,
|
|
@@ -2100,6 +4137,11 @@ export {
|
|
|
2100
4137
|
* The schema is small and stable; external consumers should treat
|
|
2101
4138
|
* unknown fields as forward-compatible additions.
|
|
2102
4139
|
*/
|
|
4140
|
+
/**
|
|
4141
|
+
* @license
|
|
4142
|
+
* Copyright 2025 Google LLC
|
|
4143
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4144
|
+
*/
|
|
2103
4145
|
/**
|
|
2104
4146
|
* @license
|
|
2105
4147
|
* Copyright 2026 Qwen Team
|