diffing 0.6.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +138 -100
- package/dist/apply-suggestion-BAuyCYk6.mjs +54 -0
- package/dist/cli-agent-BkkRbKqq.mjs +513 -0
- package/dist/cli-agent-BlMtx1Tx.mjs +736 -0
- package/dist/cli-agent-C4Fb90GL.mjs +728 -0
- package/dist/cli-agent-ChALuNbw.mjs +841 -0
- package/dist/cli-agent-D71pq2ep.mjs +841 -0
- package/dist/cli-agent-qBT8clNM.mjs +841 -0
- package/dist/cli-gh-6qiblgj7.mjs +150 -0
- package/dist/cli-gh-D5OCnjNe.mjs +150 -0
- package/dist/cli-gh-DgqscIkg.mjs +147 -0
- package/dist/cli-gh-KjPnyST6.mjs +150 -0
- package/dist/cli.mjs +38 -66
- package/dist/client/assets/{angular-html-BotC7B4-.js → angular-html-Dh86jUvr.js} +1 -1
- package/dist/client/assets/{angular-ts-DQTHeTJm.js → angular-ts-Bh4wRJtT.js} +1 -1
- package/dist/client/assets/{apl-Bwz5Dpd_.js → apl-BSXBHduT.js} +1 -1
- package/dist/client/assets/{arc-D-TSgv-7.js → arc-CRQlhBrV.js} +1 -1
- package/dist/client/assets/{architectureDiagram-3BPJPVTR-DLe25t8t.js → architectureDiagram-3BPJPVTR-BXq3UvEv.js} +1 -1
- package/dist/client/assets/{astro-DjkoD2fR.js → astro-Bh00klUw.js} +1 -1
- package/dist/client/assets/{blade-Cap2uJIA.js → blade-CisA3zhm.js} +1 -1
- package/dist/client/assets/{blockDiagram-GPEHLZMM-B-bihLYO.js → blockDiagram-GPEHLZMM-Bw-PTAJr.js} +1 -1
- package/dist/client/assets/{c-C7JoAATL.js → c-D-pHVWrf.js} +1 -1
- package/dist/client/assets/{c4Diagram-AAUBKEIU-CoCcjXEC.js → c4Diagram-AAUBKEIU-C0XyiqC_.js} +1 -1
- package/dist/client/assets/channel-CDxgOu-3.js +1 -0
- package/dist/client/assets/{chunk-2J33WTMH-Sm_uQrGq.js → chunk-2J33WTMH-DekjZXNj.js} +1 -1
- package/dist/client/assets/{chunk-3OPIFGDE-Ct83Sp4P.js → chunk-3OPIFGDE-BtROpcL8.js} +1 -1
- package/dist/client/assets/{chunk-4BX2VUAB-DrqChk1a.js → chunk-4BX2VUAB-C92XLTlx.js} +1 -1
- package/dist/client/assets/{chunk-55IACEB6-CodeFb9E.js → chunk-55IACEB6-CceUgh8N.js} +1 -1
- package/dist/client/assets/{chunk-5ZQYHXKU-CpEx4dmS.js → chunk-5ZQYHXKU-DSbYPAe7.js} +1 -1
- package/dist/client/assets/{chunk-727SXJPM-D3U6TUmk.js → chunk-727SXJPM-DVzUJa-9.js} +1 -1
- package/dist/client/assets/{chunk-AQP2D5EJ-Dr-_DyBy.js → chunk-AQP2D5EJ-6n3Z_IFa.js} +1 -1
- package/dist/client/assets/{chunk-BSJP7CBP-Bnyml8mU.js → chunk-BSJP7CBP-BgVLnkxr.js} +1 -1
- package/dist/client/assets/{chunk-CSCIHK7Q-BJSFGTlk.js → chunk-CSCIHK7Q-CkGMXntp.js} +1 -1
- package/dist/client/assets/{chunk-FMBD7UC4-BqiwDIwx.js → chunk-FMBD7UC4-BM8lrLBo.js} +1 -1
- package/dist/client/assets/{chunk-KSCS5N6A-D-H-HBRn.js → chunk-KSCS5N6A-Bm2pjMQg.js} +1 -1
- package/dist/client/assets/{chunk-L5ZTLDWV-CVMSAF7h.js → chunk-L5ZTLDWV-VL17ikXM.js} +1 -1
- package/dist/client/assets/{chunk-LZXEDZCA-BQsF6HF3.js → chunk-LZXEDZCA-B629g67M.js} +2 -2
- package/dist/client/assets/{chunk-ND2GUHAM-eoM20ySO.js → chunk-ND2GUHAM-CjZGwhKA.js} +1 -1
- package/dist/client/assets/{chunk-NZK2D7GU-B1iU0i4a.js → chunk-NZK2D7GU-DGsLjESY.js} +1 -1
- package/dist/client/assets/{chunk-O5CBEL6O-CcIPuHUC.js → chunk-O5CBEL6O-EyUrGY3l.js} +1 -1
- package/dist/client/assets/chunk-QZHKN3VN-DJCeMpP8.js +1 -0
- package/dist/client/assets/chunk-WU5MYG2G-BbuOkMaH.js +1 -0
- package/dist/client/assets/{chunk-XPW4576I-jSj3E63-.js → chunk-XPW4576I-C2TeAQmQ.js} +1 -1
- package/dist/client/assets/classDiagram-4FO5ZUOK-D5oJbiED.js +1 -0
- package/dist/client/assets/classDiagram-v2-Q7XG4LA2-5MJt5ZxQ.js +1 -0
- package/dist/client/assets/{cobol-DkiK_msx.js → cobol-DsRZMP16.js} +1 -1
- package/dist/client/assets/{coffee-f7z4wIqP.js → coffee-DP0-MMsE.js} +1 -1
- package/dist/client/assets/{cose-bilkent-S5V4N54A-Dikk-RXu.js → cose-bilkent-S5V4N54A-DMmK0b0W.js} +1 -1
- package/dist/client/assets/{cpp-DB_FV8ps.js → cpp-B6XYbbze.js} +1 -1
- package/dist/client/assets/{crystal-BRBnyZFz.js → crystal-dBLPFLMp.js} +1 -1
- package/dist/client/assets/{css-GpNBLqRF.js → css-tgwuSZL0.js} +1 -1
- package/dist/client/assets/{dagre-BM42HDAG-tErJNBcm.js → dagre-BM42HDAG-CgNkf0TR.js} +1 -1
- package/dist/client/assets/{diagram-2AECGRRQ-D0-DgitC.js → diagram-2AECGRRQ-ocAqn0LV.js} +1 -1
- package/dist/client/assets/{diagram-5GNKFQAL-DqMjQTtW.js → diagram-5GNKFQAL-CB1Yo4Gc.js} +1 -1
- package/dist/client/assets/{diagram-KO2AKTUF-CP7V9tqS.js → diagram-KO2AKTUF-B2vpMBRT.js} +1 -1
- package/dist/client/assets/{diagram-LMA3HP47-tCGdsz4p.js → diagram-LMA3HP47-DuxZOaQS.js} +1 -1
- package/dist/client/assets/{diagram-OG6HWLK6-DtDnaqso.js → diagram-OG6HWLK6-CkShDmcv.js} +1 -1
- package/dist/client/assets/{dist-B5Eb-OKZ.js → dist-BgZIvk5w.js} +1 -1
- package/dist/client/assets/{edge-DqnWvViE.js → edge-Bokubn8E.js} +1 -1
- package/dist/client/assets/{elixir-BSuxOIIa.js → elixir-BEzcf3Xg.js} +1 -1
- package/dist/client/assets/{elm-B6U1Is0L.js → elm-vJyLTAER.js} +1 -1
- package/dist/client/assets/{erDiagram-TEJ5UH35-C5S0U2x3.js → erDiagram-TEJ5UH35-BFV1NQAH.js} +1 -1
- package/dist/client/assets/{erb-kK3QM18H.js → erb-DDkAmjBS.js} +1 -1
- package/dist/client/assets/{flowDiagram-I6XJVG4X-NNqmBYk2.js → flowDiagram-I6XJVG4X-DU8xbFdO.js} +1 -1
- package/dist/client/assets/{ganttDiagram-6RSMTGT7-8ng09X8S.js → ganttDiagram-6RSMTGT7-DCb7Qh0c.js} +1 -1
- package/dist/client/assets/{git-rebase-BDKTWtxr.js → git-rebase-DWcifAdK.js} +1 -1
- package/dist/client/assets/{gitGraphDiagram-PVQCEYII-D_HBvwVl.js → gitGraphDiagram-PVQCEYII-C_Tw-piE.js} +1 -1
- package/dist/client/assets/{glimmer-js-BthGlRra.js → glimmer-js--HDru5ys.js} +1 -1
- package/dist/client/assets/{glimmer-ts-CS8Z9z2h.js → glimmer-ts-DAdXisHh.js} +1 -1
- package/dist/client/assets/{glsl-C5ERhU2v.js → glsl-CmIRlJht.js} +1 -1
- package/dist/client/assets/{graphql-sfgTqbIp.js → graphql-tx4-7I3U.js} +1 -1
- package/dist/client/assets/{hack-EeOmOf4N.js → hack-yA0XjhF7.js} +1 -1
- package/dist/client/assets/{haml-DVf5-Ndq.js → haml-BR5MUE1b.js} +1 -1
- package/dist/client/assets/{handlebars-BSLgW49o.js → handlebars-BPOF7iOf.js} +1 -1
- package/dist/client/assets/{html-DoOSf-hz.js → html-BbteRk96.js} +1 -1
- package/dist/client/assets/{html-derivative-BM0DLd1Z.js → html-derivative-59NNrcMm.js} +1 -1
- package/dist/client/assets/{http-d2p18QI6.js → http-D3odGQI4.js} +1 -1
- package/dist/client/assets/{hurl-CH2I2f66.js → hurl-BLTj8fPj.js} +1 -1
- package/dist/client/assets/index-BZcKS8Sb.css +1 -0
- package/dist/client/assets/{index-CJI1E0bV.js → index-DHXpO7eS.js} +137 -104
- package/dist/client/assets/{infoDiagram-5YYISTIA-Cgg2FsCN.js → infoDiagram-5YYISTIA-CI6hJyRl.js} +1 -1
- package/dist/client/assets/{ishikawaDiagram-YF4QCWOH-pYRQFecr.js → ishikawaDiagram-YF4QCWOH-gMS1P1LQ.js} +1 -1
- package/dist/client/assets/{java-BFiqhlWm.js → java-CsViuzs_.js} +1 -1
- package/dist/client/assets/{javascript-YWa7zRPZ.js → javascript-DQgsl2Dh.js} +1 -1
- package/dist/client/assets/{jinja-h_UdmDAn.js → jinja-DQNp2NUA.js} +1 -1
- package/dist/client/assets/{jison-cOvcjPYz.js → jison-CDDOZQdc.js} +1 -1
- package/dist/client/assets/{journeyDiagram-JHISSGLW-D3EBlW7m.js → journeyDiagram-JHISSGLW-BTQ_Ws_h.js} +1 -1
- package/dist/client/assets/{json-DT0GjoRi.js → json-DP4TqCuE.js} +1 -1
- package/dist/client/assets/{jsx-BaYjgZVH.js → jsx-Df5hn0Tf.js} +1 -1
- package/dist/client/assets/{julia-Cg36ySxC.js → julia--u33yGLi.js} +1 -1
- package/dist/client/assets/{just-KYC4U1y4.js → just-BCpMmSOD.js} +1 -1
- package/dist/client/assets/{kanban-definition-UN3LZRKU-DA42mt4M.js → kanban-definition-UN3LZRKU-CvqGbLR_.js} +1 -1
- package/dist/client/assets/{latex-DpuXxlsH.js → latex-D8D4jZGw.js} +1 -1
- package/dist/client/assets/{line-D91vf_Yl.js → line-DBgaS_RW.js} +1 -1
- package/dist/client/assets/{linear-CL7WzrL5.js → linear-RQVzKNVU.js} +1 -1
- package/dist/client/assets/{liquid-Dua3sjBU.js → liquid-CkH85f12.js} +1 -1
- package/dist/client/assets/{lua-CpDMEbOh.js → lua-iKoV7WE3.js} +1 -1
- package/dist/client/assets/{marko-rM0rxrJ5.js → marko-N8ScQSqT.js} +1 -1
- package/dist/client/assets/{mdc-D-E4W6ne.js → mdc-BvHTOxsp.js} +1 -1
- package/dist/client/assets/{mermaid-parser.core-BLQDdrsf.js → mermaid-parser.core-CwDRwMR7.js} +1 -1
- package/dist/client/assets/{mermaid.core-BBR2Otc_.js → mermaid.core-D2LDJgXO.js} +3 -3
- package/dist/client/assets/{mindmap-definition-RKZ34NQL-DMrApv-_.js → mindmap-definition-RKZ34NQL-FRnPh642.js} +1 -1
- package/dist/client/assets/{nginx-wZWgcQLM.js → nginx-DGgvASuI.js} +1 -1
- package/dist/client/assets/{nim-Bh7OZsZh.js → nim-D-v9hzvR.js} +1 -1
- package/dist/client/assets/{perl-DtEqNC_V.js → perl-CG8leJ8K.js} +1 -1
- package/dist/client/assets/{php-DX7bHce8.js → php-DvkeSP10.js} +1 -1
- package/dist/client/assets/{pieDiagram-4H26LBE5-B2f9_PO6.js → pieDiagram-4H26LBE5-CepOg38Y.js} +1 -1
- package/dist/client/assets/{pug-r67inScB.js → pug-B4T7-xaL.js} +1 -1
- package/dist/client/assets/{qml-f1bOqUwX.js → qml-BE6D5Bxn.js} +1 -1
- package/dist/client/assets/{quadrantDiagram-W4KKPZXB-DgdKno5G.js → quadrantDiagram-W4KKPZXB-DOFAsP60.js} +1 -1
- package/dist/client/assets/{r-Cpm-_vkr.js → r-BKZg9Mcq.js} +1 -1
- package/dist/client/assets/{razor-CEymfLfo.js → razor-D-02oaxL.js} +1 -1
- package/dist/client/assets/{regexp-DCnAJwuE.js → regexp-BDFvAR1k.js} +1 -1
- package/dist/client/assets/{requirementDiagram-4Y6WPE33-BIRl56-J.js → requirementDiagram-4Y6WPE33-BBVNH-2W.js} +1 -1
- package/dist/client/assets/review-export-D5iIl46_.js +7 -0
- package/dist/client/assets/{rst-Cm1M2uc8.js → rst-BcT26OSj.js} +1 -1
- package/dist/client/assets/{ruby-B49kkGT0.js → ruby-Cq_YQvFT.js} +1 -1
- package/dist/client/assets/{sankeyDiagram-5OEKKPKP-7XZZmV9N.js → sankeyDiagram-5OEKKPKP-BQfCTbBX.js} +1 -1
- package/dist/client/assets/{sas-BOXC9Iq4.js → sas-DN9oTxKY.js} +1 -1
- package/dist/client/assets/{scss-DU2HGWZP.js → scss-BK_sp3z2.js} +1 -1
- package/dist/client/assets/{sequenceDiagram-3UESZ5HK-CtIjo3i9.js → sequenceDiagram-3UESZ5HK-CKAW2eyx.js} +1 -1
- package/dist/client/assets/{shellscript-YFjx1NBH.js → shellscript-CvNpOu0J.js} +1 -1
- package/dist/client/assets/{shellsession-4zfyiKm-.js → shellsession-8-yj2cJj.js} +1 -1
- package/dist/client/assets/{soy-CRExh-ql.js → soy-C6Q9atPF.js} +1 -1
- package/dist/client/assets/{sql-2Iw6Ta1F.js → sql-CBL3BuHb.js} +1 -1
- package/dist/client/assets/{src-Dn3_YOHM.js → src-D-VUWIK5.js} +1 -1
- package/dist/client/assets/{stata-DflAcpzB.js → stata-B2Q7Vqzu.js} +1 -1
- package/dist/client/assets/{stateDiagram-AJRCARHV-D_w9F0BN.js → stateDiagram-AJRCARHV-BGJiv0qO.js} +1 -1
- package/dist/client/assets/stateDiagram-v2-BHNVJYJU-CdUgMghp.js +1 -0
- package/dist/client/assets/{surrealql-CMprrxj1.js → surrealql-C-A2N8vk.js} +1 -1
- package/dist/client/assets/{svelte-Bb_Bxt4a.js → svelte-VAut_-uy.js} +1 -1
- package/dist/client/assets/{templ-MJ_5gYAf.js → templ-mUetM--K.js} +1 -1
- package/dist/client/assets/{tex-BQ33E56K.js → tex-BT53mWGp.js} +1 -1
- package/dist/client/assets/{timeline-definition-PNZ67QCA-BzgsTjf8.js → timeline-definition-PNZ67QCA-lVwMt1rk.js} +1 -1
- package/dist/client/assets/{ts-tags-Cddttr6f.js → ts-tags-VkIOkgLN.js} +1 -1
- package/dist/client/assets/{tsx-BFchi_eS.js → tsx-D_aaGHTf.js} +1 -1
- package/dist/client/assets/{twig-CA8ssIYI.js → twig-CufwqKkk.js} +1 -1
- package/dist/client/assets/{typescript-tdiTR9sY.js → typescript-osFD2CEV.js} +1 -1
- package/dist/client/assets/{vennDiagram-CIIHVFJN-CMjPo4Qf.js → vennDiagram-CIIHVFJN-_K9NtpA5.js} +1 -1
- package/dist/client/assets/{vue-BbZNP0Rd.js → vue-Bub1rXmL.js} +1 -1
- package/dist/client/assets/{vue-html-02g5ipYx.js → vue-html-CN3x2pYg.js} +1 -1
- package/dist/client/assets/{vue-vine-9lCbaeLR.js → vue-vine-C0csYkGm.js} +1 -1
- package/dist/client/assets/{wardleyDiagram-YWT4CUSO-Cy6qwCgU.js → wardleyDiagram-YWT4CUSO-B2jTMfra.js} +1 -1
- package/dist/client/assets/{xml-BPPCB2P8.js → xml-my6OjeGb.js} +1 -1
- package/dist/client/assets/{xsl-1-u1Beet.js → xsl-Bx0YVn26.js} +1 -1
- package/dist/client/assets/{xychartDiagram-2RQKCTM6-q9ysdRiS.js → xychartDiagram-2RQKCTM6-DfZ4YYXU.js} +1 -1
- package/dist/client/assets/{yaml-79W-bav7.js → yaml-DanwOypk.js} +1 -1
- package/dist/client/favicon.svg +40 -0
- package/dist/client/index.html +7 -3
- package/dist/comment-format-B5AcGcR-.mjs +118 -0
- package/dist/comment-format-CKTt0fzi.mjs +113 -0
- package/dist/comment-format-DG_PAehC.mjs +117 -0
- package/dist/completions-ZSKYvW06.mjs +126 -0
- package/dist/diff-fingerprint-CWa-soYD.mjs +78 -0
- package/dist/doctor-q6oA64hm.mjs +195 -0
- package/dist/find-tui-binary-CRpbYXLC.mjs +52 -0
- package/dist/git-Czo96ymM.mjs +789 -0
- package/dist/git-DaP_1rVy.mjs +2 -0
- package/dist/github-C9GLNak7.mjs +2 -0
- package/dist/github-HfIlIS-J.mjs +525 -0
- package/dist/mcp-7eXATtez.mjs +1334 -0
- package/dist/mcp-BBk8AIO4.mjs +1334 -0
- package/dist/mcp-BJxrW9dS.mjs +1334 -0
- package/dist/mcp-BgZO-iXI.mjs +1047 -0
- package/dist/mcp-BhT5GgYF.mjs +1334 -0
- package/dist/mcp-BsGdNA88.mjs +1334 -0
- package/dist/mcp-BxBm3d5l.mjs +1047 -0
- package/dist/mcp-Ca9QAlJ0.mjs +1334 -0
- package/dist/mcp-CphIUJUg.mjs +1047 -0
- package/dist/mcp-eor39qmD.mjs +1352 -0
- package/dist/plan-format-BQyx--mp.mjs +144 -0
- package/dist/plan-format-Bi46QS0m.mjs +132 -0
- package/dist/review-export-BoV0884M.mjs +66 -0
- package/dist/review-export-CKTI5Od_.mjs +66 -0
- package/dist/review-export-DjxKBeIZ.mjs +66 -0
- package/dist/server-AisDKkpW.mjs +3227 -0
- package/dist/server-BUsRhL1B.mjs +3283 -0
- package/dist/server-BZBFquG8.mjs +3875 -0
- package/dist/server-Bc2m9R5-.mjs +3263 -0
- package/dist/server-Bmt57wV5.mjs +3875 -0
- package/dist/server-C105rwQw.mjs +3283 -0
- package/dist/server-D0Ni8PKP.mjs +3252 -0
- package/dist/server-D2xEbakj.mjs +3874 -0
- package/dist/server-TOElOuNP.mjs +3258 -0
- package/dist/server-lock-DaSx7DjW.mjs +1049 -0
- package/dist/server-lock-uqSdDYeR.mjs +153 -0
- package/dist/settings-BVnzGFL7.mjs +478 -0
- package/dist/update-check-fe0tR8tx.mjs +129 -0
- package/package.json +1 -1
- package/dist/client/assets/channel-h7e2aSOW.js +0 -1
- package/dist/client/assets/chunk-QZHKN3VN-HzjMGBiA.js +0 -1
- package/dist/client/assets/chunk-WU5MYG2G-D_yZ0nqc.js +0 -1
- package/dist/client/assets/classDiagram-4FO5ZUOK-eToeXGJV.js +0 -1
- package/dist/client/assets/classDiagram-v2-Q7XG4LA2-D_EfzX89.js +0 -1
- package/dist/client/assets/index-Ct9zZxJ5.css +0 -1
- package/dist/client/assets/stateDiagram-v2-BHNVJYJU-Brvg0TWH.js +0 -1
|
@@ -0,0 +1,789 @@
|
|
|
1
|
+
import { execFile, execFileSync } from "node:child_process";
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
|
+
import { basename, join, relative, resolve, sep } from "node:path";
|
|
4
|
+
import { readFileSync } from "node:fs";
|
|
5
|
+
import { promisify } from "node:util";
|
|
6
|
+
import { readFile } from "node:fs/promises";
|
|
7
|
+
import { homedir } from "node:os";
|
|
8
|
+
import { parseSync } from "editorconfig";
|
|
9
|
+
//#region src/lib/path.ts
|
|
10
|
+
function decodeAndNormalize(p) {
|
|
11
|
+
let decoded = p;
|
|
12
|
+
try {
|
|
13
|
+
decoded = decodeURIComponent(p);
|
|
14
|
+
} catch {}
|
|
15
|
+
return decoded.replace(/\\/g, "/");
|
|
16
|
+
}
|
|
17
|
+
function toSafeRelativePath(filePath, baseDir) {
|
|
18
|
+
const normalized = decodeAndNormalize(filePath);
|
|
19
|
+
if (normalized.includes("..") || normalized.includes("\0")) return null;
|
|
20
|
+
const resolved = resolve(baseDir, normalized);
|
|
21
|
+
const resolvedBase = resolve(baseDir);
|
|
22
|
+
if (!(resolved === resolvedBase || resolved.startsWith(resolvedBase + sep))) return null;
|
|
23
|
+
return relative(resolvedBase, resolved);
|
|
24
|
+
}
|
|
25
|
+
function isSafePath(relativePath, baseDir) {
|
|
26
|
+
return toSafeRelativePath(relativePath, baseDir) !== null;
|
|
27
|
+
}
|
|
28
|
+
//#endregion
|
|
29
|
+
//#region src/lib/git.ts
|
|
30
|
+
const execFileAsync = promisify(execFile);
|
|
31
|
+
function splitLines(content) {
|
|
32
|
+
return content.split(/\r\n|\n|\r/);
|
|
33
|
+
}
|
|
34
|
+
function isBinaryFile(absolutePath) {
|
|
35
|
+
try {
|
|
36
|
+
const buffer = readFileSync(absolutePath);
|
|
37
|
+
const bytesToCheck = Math.min(buffer.length, 8192);
|
|
38
|
+
for (let i = 0; i < bytesToCheck; i++) if (buffer[i] === 0) return true;
|
|
39
|
+
return false;
|
|
40
|
+
} catch {
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
function getFileContent(filePath, version) {
|
|
45
|
+
const root = getRepoRoot();
|
|
46
|
+
const relPath = toSafeRelativePath(filePath, root);
|
|
47
|
+
if (!relPath) return null;
|
|
48
|
+
const resolved = resolve(root, relPath);
|
|
49
|
+
if (version === "new") try {
|
|
50
|
+
return readFileSync(resolved);
|
|
51
|
+
} catch {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
try {
|
|
55
|
+
return execFileSync("git", ["show", `HEAD:${relPath}`], {
|
|
56
|
+
stdio: "pipe",
|
|
57
|
+
maxBuffer: 50 * 1024 * 1024
|
|
58
|
+
});
|
|
59
|
+
} catch {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
let cachedRepoRoot = null;
|
|
64
|
+
const editorConfigCache = /* @__PURE__ */ new Map();
|
|
65
|
+
function isGitRepo() {
|
|
66
|
+
try {
|
|
67
|
+
execFileSync("git", ["rev-parse", "--is-inside-work-tree"], { stdio: "pipe" });
|
|
68
|
+
return true;
|
|
69
|
+
} catch {
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
function getRepoRoot() {
|
|
74
|
+
if (cachedRepoRoot !== null) return cachedRepoRoot;
|
|
75
|
+
cachedRepoRoot = execFileSync("git", ["rev-parse", "--show-toplevel"], { encoding: "utf-8" }).trim();
|
|
76
|
+
return cachedRepoRoot;
|
|
77
|
+
}
|
|
78
|
+
function getRepoName() {
|
|
79
|
+
return basename(getRepoRoot());
|
|
80
|
+
}
|
|
81
|
+
function getBranchName() {
|
|
82
|
+
try {
|
|
83
|
+
return execFileSync("git", [
|
|
84
|
+
"rev-parse",
|
|
85
|
+
"--abbrev-ref",
|
|
86
|
+
"HEAD"
|
|
87
|
+
], {
|
|
88
|
+
stdio: "pipe",
|
|
89
|
+
encoding: "utf-8"
|
|
90
|
+
}).trim();
|
|
91
|
+
} catch {
|
|
92
|
+
return "";
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
const DIFF_FLAGS = [
|
|
96
|
+
"--no-ext-diff",
|
|
97
|
+
"--no-textconv",
|
|
98
|
+
"--no-color"
|
|
99
|
+
];
|
|
100
|
+
function getTabSizeForFiles(filePaths) {
|
|
101
|
+
const root = getRepoRoot();
|
|
102
|
+
const result = {};
|
|
103
|
+
for (const filePath of filePaths) try {
|
|
104
|
+
const config = parseSync(join(root, filePath), { cache: editorConfigCache });
|
|
105
|
+
const size = config.tab_width ?? (config.indent_size === "tab" ? void 0 : config.indent_size);
|
|
106
|
+
if (typeof size === "number") result[filePath] = size;
|
|
107
|
+
} catch {}
|
|
108
|
+
return result;
|
|
109
|
+
}
|
|
110
|
+
async function getRepoRootAsync() {
|
|
111
|
+
if (cachedRepoRoot !== null) return cachedRepoRoot;
|
|
112
|
+
try {
|
|
113
|
+
const { stdout } = await execFileAsync("git", ["rev-parse", "--show-toplevel"], { encoding: "utf-8" });
|
|
114
|
+
cachedRepoRoot = stdout.trim();
|
|
115
|
+
return cachedRepoRoot;
|
|
116
|
+
} catch {
|
|
117
|
+
return "";
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
async function getUntrackedFilePathsAsync() {
|
|
121
|
+
try {
|
|
122
|
+
const { stdout } = await execFileAsync("git", [
|
|
123
|
+
"ls-files",
|
|
124
|
+
"--others",
|
|
125
|
+
"--exclude-standard"
|
|
126
|
+
], {
|
|
127
|
+
encoding: "utf-8",
|
|
128
|
+
maxBuffer: 50 * 1024 * 1024
|
|
129
|
+
});
|
|
130
|
+
const trimmed = stdout.trim();
|
|
131
|
+
return trimmed ? trimmed.split("\n") : [];
|
|
132
|
+
} catch {
|
|
133
|
+
return [];
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
async function getUntrackedFilesDiffAsync() {
|
|
137
|
+
const root = await getRepoRootAsync();
|
|
138
|
+
const filePaths = await getUntrackedFilePathsAsync();
|
|
139
|
+
if (filePaths.length === 0) return "";
|
|
140
|
+
const patches = [];
|
|
141
|
+
const fileDiffPromises = filePaths.map(async (file) => {
|
|
142
|
+
const absolutePath = join(root, file);
|
|
143
|
+
if (isBinaryFile(absolutePath)) return [
|
|
144
|
+
`diff --git a/${file} b/${file}`,
|
|
145
|
+
"new file mode 100644",
|
|
146
|
+
"index 0000000..0000001",
|
|
147
|
+
`Binary files /dev/null and b/${file} differ`
|
|
148
|
+
].join("\n");
|
|
149
|
+
else try {
|
|
150
|
+
const lines = splitLines(await readFile(absolutePath, "utf-8"));
|
|
151
|
+
const diffLines = lines.map((l) => `+${l}`);
|
|
152
|
+
return [
|
|
153
|
+
`diff --git a/${file} b/${file}`,
|
|
154
|
+
"new file mode 100644",
|
|
155
|
+
"index 0000000..0000001",
|
|
156
|
+
"--- /dev/null",
|
|
157
|
+
`+++ b/${file}`,
|
|
158
|
+
`@@ -0,0 +1,${lines.length} @@`,
|
|
159
|
+
...diffLines
|
|
160
|
+
].join("\n");
|
|
161
|
+
} catch {
|
|
162
|
+
return "";
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
const results = await Promise.all(fileDiffPromises);
|
|
166
|
+
for (const r of results) if (r) patches.push(r);
|
|
167
|
+
return patches.length > 0 ? "\n" + patches.join("\n") : "";
|
|
168
|
+
}
|
|
169
|
+
async function getCustomGitDiffAsync(args) {
|
|
170
|
+
try {
|
|
171
|
+
const { stdout } = await execFileAsync("git", [
|
|
172
|
+
"diff",
|
|
173
|
+
...DIFF_FLAGS,
|
|
174
|
+
...args
|
|
175
|
+
], {
|
|
176
|
+
encoding: "utf-8",
|
|
177
|
+
maxBuffer: 50 * 1024 * 1024
|
|
178
|
+
});
|
|
179
|
+
return stdout;
|
|
180
|
+
} catch {
|
|
181
|
+
return "";
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
async function getGitDiffAsync(options = {}) {
|
|
185
|
+
const unstagedPromise = execFileAsync("git", ["diff", ...DIFF_FLAGS], {
|
|
186
|
+
encoding: "utf-8",
|
|
187
|
+
maxBuffer: 50 * 1024 * 1024
|
|
188
|
+
}).then(({ stdout }) => stdout).catch(() => "");
|
|
189
|
+
const stagedPromise = options.staged ? execFileAsync("git", [
|
|
190
|
+
"diff",
|
|
191
|
+
...DIFF_FLAGS,
|
|
192
|
+
"--staged"
|
|
193
|
+
], {
|
|
194
|
+
encoding: "utf-8",
|
|
195
|
+
maxBuffer: 50 * 1024 * 1024
|
|
196
|
+
}).then(({ stdout }) => stdout).catch(() => "") : Promise.resolve("");
|
|
197
|
+
const untrackedPromise = options.untracked ? getUntrackedFilesDiffAsync() : Promise.resolve("");
|
|
198
|
+
const [unstaged, staged, untrackedPatch] = await Promise.all([
|
|
199
|
+
unstagedPromise,
|
|
200
|
+
stagedPromise,
|
|
201
|
+
untrackedPromise
|
|
202
|
+
]);
|
|
203
|
+
const parts = [];
|
|
204
|
+
if (unstaged) parts.push(unstaged);
|
|
205
|
+
if (staged) parts.push(staged);
|
|
206
|
+
if (untrackedPatch) parts.push(untrackedPatch);
|
|
207
|
+
return parts.join("\n");
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Lists every file the working tree knows about (tracked + untracked,
|
|
211
|
+
* excluding standard-ignored paths). Used by the Phase D file viewer to
|
|
212
|
+
* power a path picker.
|
|
213
|
+
*/
|
|
214
|
+
function listRepoFiles() {
|
|
215
|
+
try {
|
|
216
|
+
const tracked = execFileSync("git", ["ls-files"], {
|
|
217
|
+
encoding: "utf-8",
|
|
218
|
+
maxBuffer: 100 * 1024 * 1024
|
|
219
|
+
}).trim();
|
|
220
|
+
const untracked = execFileSync("git", [
|
|
221
|
+
"ls-files",
|
|
222
|
+
"--others",
|
|
223
|
+
"--exclude-standard"
|
|
224
|
+
], {
|
|
225
|
+
encoding: "utf-8",
|
|
226
|
+
maxBuffer: 100 * 1024 * 1024
|
|
227
|
+
}).trim();
|
|
228
|
+
const set = /* @__PURE__ */ new Set();
|
|
229
|
+
for (const list of [tracked, untracked]) {
|
|
230
|
+
if (!list) continue;
|
|
231
|
+
for (const line of list.split("\n")) if (line) set.add(line);
|
|
232
|
+
}
|
|
233
|
+
return [...set].sort();
|
|
234
|
+
} catch {
|
|
235
|
+
return [];
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Reports whether the repo is currently in the middle of a merge and which
|
|
240
|
+
* files have unresolved conflicts. Used by the Phase C merge-conflict UI to
|
|
241
|
+
* decide when to render the @pierre/diffs UnresolvedFile component.
|
|
242
|
+
*/
|
|
243
|
+
function getMergeStatus() {
|
|
244
|
+
let root;
|
|
245
|
+
try {
|
|
246
|
+
root = getRepoRoot();
|
|
247
|
+
} catch {
|
|
248
|
+
return {
|
|
249
|
+
inMerge: false,
|
|
250
|
+
conflicts: []
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
const mergeHead = join(root, ".git", "MERGE_HEAD");
|
|
254
|
+
let inMerge = false;
|
|
255
|
+
try {
|
|
256
|
+
readFileSync(mergeHead);
|
|
257
|
+
inMerge = true;
|
|
258
|
+
} catch {
|
|
259
|
+
inMerge = false;
|
|
260
|
+
}
|
|
261
|
+
let conflicts = [];
|
|
262
|
+
try {
|
|
263
|
+
const out = execFileSync("git", [
|
|
264
|
+
"diff",
|
|
265
|
+
"--name-only",
|
|
266
|
+
"--diff-filter=U"
|
|
267
|
+
], {
|
|
268
|
+
encoding: "utf-8",
|
|
269
|
+
maxBuffer: 50 * 1024 * 1024
|
|
270
|
+
}).trim();
|
|
271
|
+
conflicts = out ? out.split("\n") : [];
|
|
272
|
+
} catch {
|
|
273
|
+
conflicts = [];
|
|
274
|
+
}
|
|
275
|
+
return {
|
|
276
|
+
inMerge: inMerge || conflicts.length > 0,
|
|
277
|
+
conflicts
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
function gitAddFile(filePath) {
|
|
281
|
+
execFileSync("git", [
|
|
282
|
+
"add",
|
|
283
|
+
"--",
|
|
284
|
+
filePath
|
|
285
|
+
], { stdio: "pipe" });
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* Returns a unified diff for a single working-tree file (covers unstaged
|
|
289
|
+
* and untracked changes). Used by the Phase E hunk-revert flow to derive
|
|
290
|
+
* the exact patch text we need to `git apply --reverse`.
|
|
291
|
+
*/
|
|
292
|
+
function getFilePatch(filePath) {
|
|
293
|
+
try {
|
|
294
|
+
const unstaged = execFileSync("git", [
|
|
295
|
+
"diff",
|
|
296
|
+
...DIFF_FLAGS,
|
|
297
|
+
"--",
|
|
298
|
+
filePath
|
|
299
|
+
], {
|
|
300
|
+
encoding: "utf-8",
|
|
301
|
+
maxBuffer: 50 * 1024 * 1024
|
|
302
|
+
});
|
|
303
|
+
if (unstaged) return unstaged;
|
|
304
|
+
} catch {}
|
|
305
|
+
try {
|
|
306
|
+
const absolutePath = join(getRepoRoot(), filePath);
|
|
307
|
+
if (isBinaryFile(absolutePath)) return "";
|
|
308
|
+
const lines = splitLines(readFileSync(absolutePath, "utf-8"));
|
|
309
|
+
return [
|
|
310
|
+
`diff --git a/${filePath} b/${filePath}`,
|
|
311
|
+
"new file mode 100644",
|
|
312
|
+
"index 0000000..0000001",
|
|
313
|
+
"--- /dev/null",
|
|
314
|
+
`+++ b/${filePath}`,
|
|
315
|
+
`@@ -0,0 +1,${lines.length} @@`,
|
|
316
|
+
...lines.map((l) => `+${l}`)
|
|
317
|
+
].join("\n");
|
|
318
|
+
} catch {
|
|
319
|
+
return "";
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
/**
|
|
323
|
+
* Extracts the Nth hunk text from a single-file patch. Returns the lines
|
|
324
|
+
* from `@@ ... @@` through the line before the next `@@` (or EOF).
|
|
325
|
+
*/
|
|
326
|
+
function extractHunk(patch, hunkIndex) {
|
|
327
|
+
const lines = patch.split("\n");
|
|
328
|
+
const hunkStarts = [];
|
|
329
|
+
for (let i = 0; i < lines.length; i++) if (lines[i].startsWith("@@ ")) hunkStarts.push(i);
|
|
330
|
+
if (hunkIndex < 0 || hunkIndex >= hunkStarts.length) return null;
|
|
331
|
+
const start = hunkStarts[hunkIndex];
|
|
332
|
+
const end = hunkIndex + 1 < hunkStarts.length ? hunkStarts[hunkIndex + 1] : lines.length;
|
|
333
|
+
return lines.slice(start, end).join("\n");
|
|
334
|
+
}
|
|
335
|
+
/** Header lines that the per-file patch needs in order for `git apply` to
|
|
336
|
+
* recognize the target. Stops just before the first `@@` hunk header. */
|
|
337
|
+
function extractPatchHeader(patch) {
|
|
338
|
+
const lines = patch.split("\n");
|
|
339
|
+
const out = [];
|
|
340
|
+
for (const line of lines) {
|
|
341
|
+
if (line.startsWith("@@ ")) break;
|
|
342
|
+
out.push(line);
|
|
343
|
+
}
|
|
344
|
+
return out.join("\n");
|
|
345
|
+
}
|
|
346
|
+
/**
|
|
347
|
+
* Reverts a single hunk from the working tree by piping a minimal patch to
|
|
348
|
+
* `git apply --reverse`. Throws if git rejects the apply (typically because
|
|
349
|
+
* the file has shifted since the diff was rendered).
|
|
350
|
+
*/
|
|
351
|
+
function revertHunk(filePath, hunkIndex) {
|
|
352
|
+
const patch = getFilePatch(filePath);
|
|
353
|
+
if (!patch) throw new Error("No diff available for this file");
|
|
354
|
+
const header = extractPatchHeader(patch);
|
|
355
|
+
const hunk = extractHunk(patch, hunkIndex);
|
|
356
|
+
if (!hunk) throw new Error(`Hunk ${hunkIndex} not found`);
|
|
357
|
+
execFileSync("git", [
|
|
358
|
+
"apply",
|
|
359
|
+
"--reverse",
|
|
360
|
+
"-"
|
|
361
|
+
], {
|
|
362
|
+
input: `${header}\n${hunk}\n`,
|
|
363
|
+
stdio: [
|
|
364
|
+
"pipe",
|
|
365
|
+
"pipe",
|
|
366
|
+
"pipe"
|
|
367
|
+
]
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
function getProjectStorageDir(customRepoRoot) {
|
|
371
|
+
const root = customRepoRoot || getRepoRoot();
|
|
372
|
+
const hash = createHash("sha256").update(root).digest("hex").slice(0, 8);
|
|
373
|
+
const repoName = basename(root);
|
|
374
|
+
return join(homedir(), ".diffing", `${repoName}-${hash}`);
|
|
375
|
+
}
|
|
376
|
+
/**
|
|
377
|
+
* Retrieves git blame for deleted lines and recent log history for the file,
|
|
378
|
+
* providing context on what commit introduced the deleted code.
|
|
379
|
+
*/
|
|
380
|
+
function getHunkHistory(filePath, deletionStart, deletionCount, revision = "HEAD") {
|
|
381
|
+
if (!isSafePath(filePath, getRepoRoot())) throw new Error("Forbidden file path");
|
|
382
|
+
const blame = [];
|
|
383
|
+
if (deletionCount > 0) try {
|
|
384
|
+
const lines = execFileSync("git", [
|
|
385
|
+
"blame",
|
|
386
|
+
"--date=format:%Y-%m-%d",
|
|
387
|
+
"-L",
|
|
388
|
+
`${deletionStart},+${deletionCount}`,
|
|
389
|
+
revision,
|
|
390
|
+
"--",
|
|
391
|
+
filePath
|
|
392
|
+
], {
|
|
393
|
+
encoding: "utf-8",
|
|
394
|
+
maxBuffer: 10 * 1024 * 1024
|
|
395
|
+
}).split("\n");
|
|
396
|
+
const commitHashes = /* @__PURE__ */ new Set();
|
|
397
|
+
for (const line of lines) {
|
|
398
|
+
if (!line.trim()) continue;
|
|
399
|
+
const match = line.match(/^(\^?[0-9a-fA-F]+) \((.*?) (\d{4}-\d{2}-\d{2}) (\d+)\)(?: (.*))?$/);
|
|
400
|
+
if (match) {
|
|
401
|
+
const [_, commit, author, date, lineNum, content] = match;
|
|
402
|
+
const cleanCommit = commit.startsWith("^") ? commit.slice(1) : commit;
|
|
403
|
+
blame.push({
|
|
404
|
+
commit: cleanCommit,
|
|
405
|
+
author: author.trim(),
|
|
406
|
+
date,
|
|
407
|
+
line: parseInt(lineNum, 10),
|
|
408
|
+
content: content || "",
|
|
409
|
+
summary: ""
|
|
410
|
+
});
|
|
411
|
+
commitHashes.add(cleanCommit);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
const commitSummaries = {};
|
|
415
|
+
for (const hash of commitHashes) try {
|
|
416
|
+
commitSummaries[hash] = execFileSync("git", [
|
|
417
|
+
"show",
|
|
418
|
+
"-s",
|
|
419
|
+
"--format=%s",
|
|
420
|
+
hash
|
|
421
|
+
], { encoding: "utf-8" }).trim();
|
|
422
|
+
} catch {
|
|
423
|
+
commitSummaries[hash] = "Unknown commit";
|
|
424
|
+
}
|
|
425
|
+
for (const entry of blame) entry.summary = commitSummaries[entry.commit] || "Unknown commit";
|
|
426
|
+
} catch (err) {
|
|
427
|
+
console.warn("Failed to get git blame for hunk history:", err.message);
|
|
428
|
+
}
|
|
429
|
+
const recentCommits = [];
|
|
430
|
+
try {
|
|
431
|
+
const logOut = execFileSync("git", [
|
|
432
|
+
"log",
|
|
433
|
+
"-n",
|
|
434
|
+
"5",
|
|
435
|
+
"--date=format:%Y-%m-%d",
|
|
436
|
+
"--format=%h|%an|%ad|%s",
|
|
437
|
+
"--",
|
|
438
|
+
filePath
|
|
439
|
+
], { encoding: "utf-8" });
|
|
440
|
+
for (const line of logOut.split("\n")) {
|
|
441
|
+
if (!line.trim()) continue;
|
|
442
|
+
const [hash, author, date, summary] = line.split("|");
|
|
443
|
+
if (hash && author && date && summary) recentCommits.push({
|
|
444
|
+
hash,
|
|
445
|
+
author: author.trim(),
|
|
446
|
+
date,
|
|
447
|
+
summary: summary.trim()
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
} catch (err) {
|
|
451
|
+
console.warn("Failed to get git log for hunk history:", err.message);
|
|
452
|
+
}
|
|
453
|
+
return {
|
|
454
|
+
blame,
|
|
455
|
+
recentCommits
|
|
456
|
+
};
|
|
457
|
+
}
|
|
458
|
+
/**
|
|
459
|
+
* Parse the output of `git log --pretty=raw -p` into `CommitInfo[]`.
|
|
460
|
+
*
|
|
461
|
+
* The `raw` format emits one record per commit:
|
|
462
|
+
*
|
|
463
|
+
* commit <sha>
|
|
464
|
+
* tree <sha>
|
|
465
|
+
* parent <sha> (zero or more; merges have ≥ 2)
|
|
466
|
+
* author <name> <email> <unix-time> <tz>
|
|
467
|
+
* committer <name> <email> <unix-time> <tz>
|
|
468
|
+
* gpgsig -----BEGIN ... (optional; continuation lines start with a single space)
|
|
469
|
+
* <indented body>
|
|
470
|
+
*
|
|
471
|
+
* <indented commit message — 4 spaces of indent>
|
|
472
|
+
*
|
|
473
|
+
* diff --git a/... b/... (the per-commit patch; may be empty)
|
|
474
|
+
* ...
|
|
475
|
+
*
|
|
476
|
+
* Records are separated by blank lines but the unambiguous boundary is the
|
|
477
|
+
* next line beginning with `commit <40-hex>` at column 0.
|
|
478
|
+
*/
|
|
479
|
+
function parseGitShowRaw(raw) {
|
|
480
|
+
if (!raw) return [];
|
|
481
|
+
const out = [];
|
|
482
|
+
const lines = raw.split("\n");
|
|
483
|
+
const starts = [];
|
|
484
|
+
for (let i = 0; i < lines.length; i++) if (/^commit [0-9a-f]{40}\b/.test(lines[i])) starts.push(i);
|
|
485
|
+
if (starts.length === 0) return [];
|
|
486
|
+
for (let s = 0; s < starts.length; s++) {
|
|
487
|
+
const begin = starts[s];
|
|
488
|
+
const end = s + 1 < starts.length ? starts[s + 1] : lines.length;
|
|
489
|
+
const info = parseSingleCommitRaw(lines.slice(begin, end));
|
|
490
|
+
if (info) out.push(info);
|
|
491
|
+
}
|
|
492
|
+
return out;
|
|
493
|
+
}
|
|
494
|
+
function parseSingleCommitRaw(lines) {
|
|
495
|
+
const shaMatch = (lines[0] ?? "").match(/^commit ([0-9a-f]{40})/);
|
|
496
|
+
if (!shaMatch) return null;
|
|
497
|
+
const sha = shaMatch[1];
|
|
498
|
+
let i = 1;
|
|
499
|
+
const parents = [];
|
|
500
|
+
let authorName = "";
|
|
501
|
+
let authorEmail = "";
|
|
502
|
+
let authorDate = "";
|
|
503
|
+
let committerName = "";
|
|
504
|
+
let committerEmail = "";
|
|
505
|
+
let committerDate = "";
|
|
506
|
+
while (i < lines.length) {
|
|
507
|
+
const line = lines[i];
|
|
508
|
+
if (line === "") break;
|
|
509
|
+
if (line.startsWith(" ")) {
|
|
510
|
+
i++;
|
|
511
|
+
continue;
|
|
512
|
+
}
|
|
513
|
+
const idx = line.indexOf(" ");
|
|
514
|
+
const key = idx === -1 ? line : line.slice(0, idx);
|
|
515
|
+
const value = idx === -1 ? "" : line.slice(idx + 1);
|
|
516
|
+
switch (key) {
|
|
517
|
+
case "tree": break;
|
|
518
|
+
case "parent":
|
|
519
|
+
if (value) parents.push(value);
|
|
520
|
+
break;
|
|
521
|
+
case "author": {
|
|
522
|
+
const ident = parseIdent(value);
|
|
523
|
+
authorName = ident.name;
|
|
524
|
+
authorEmail = ident.email;
|
|
525
|
+
authorDate = ident.date;
|
|
526
|
+
break;
|
|
527
|
+
}
|
|
528
|
+
case "committer": {
|
|
529
|
+
const ident = parseIdent(value);
|
|
530
|
+
committerName = ident.name;
|
|
531
|
+
committerEmail = ident.email;
|
|
532
|
+
committerDate = ident.date;
|
|
533
|
+
break;
|
|
534
|
+
}
|
|
535
|
+
default: break;
|
|
536
|
+
}
|
|
537
|
+
i++;
|
|
538
|
+
}
|
|
539
|
+
if (i < lines.length && lines[i] === "") i++;
|
|
540
|
+
const msgLines = [];
|
|
541
|
+
while (i < lines.length) {
|
|
542
|
+
const line = lines[i];
|
|
543
|
+
if (line.startsWith(" ")) {
|
|
544
|
+
msgLines.push(line.slice(4));
|
|
545
|
+
i++;
|
|
546
|
+
continue;
|
|
547
|
+
}
|
|
548
|
+
if (line === "") {
|
|
549
|
+
msgLines.push("");
|
|
550
|
+
i++;
|
|
551
|
+
continue;
|
|
552
|
+
}
|
|
553
|
+
break;
|
|
554
|
+
}
|
|
555
|
+
const { subject, body } = splitMessage(msgLines);
|
|
556
|
+
let patchStart = -1;
|
|
557
|
+
for (let j = i; j < lines.length; j++) {
|
|
558
|
+
const line = lines[j];
|
|
559
|
+
if (line.startsWith("diff --git ") || line.startsWith("Binary files ")) {
|
|
560
|
+
patchStart = j;
|
|
561
|
+
break;
|
|
562
|
+
}
|
|
563
|
+
if (line === "") continue;
|
|
564
|
+
break;
|
|
565
|
+
}
|
|
566
|
+
const patch = patchStart === -1 ? "" : lines.slice(patchStart).join("\n").replace(/\n+$/, "\n");
|
|
567
|
+
return {
|
|
568
|
+
sha,
|
|
569
|
+
shortSha: sha.slice(0, 7),
|
|
570
|
+
parents,
|
|
571
|
+
subject,
|
|
572
|
+
body,
|
|
573
|
+
authorName,
|
|
574
|
+
authorEmail,
|
|
575
|
+
authorDate,
|
|
576
|
+
committerName,
|
|
577
|
+
committerEmail,
|
|
578
|
+
committerDate,
|
|
579
|
+
patch
|
|
580
|
+
};
|
|
581
|
+
}
|
|
582
|
+
/**
|
|
583
|
+
* Parse a `Name <email> <unix-seconds> <±HHMM>` ident line into structured
|
|
584
|
+
* fields. The email is bracketed; the timestamp is split out and converted to
|
|
585
|
+
* ISO 8601 for consumers.
|
|
586
|
+
*/
|
|
587
|
+
function parseIdent(value) {
|
|
588
|
+
const match = value.match(/^(.*?)\s+<([^>]*)>\s+(\d+)\s+([+-]\d{4})$/);
|
|
589
|
+
if (!match) return {
|
|
590
|
+
name: value,
|
|
591
|
+
email: "",
|
|
592
|
+
date: ""
|
|
593
|
+
};
|
|
594
|
+
const [, name, email, secs, tz] = match;
|
|
595
|
+
return {
|
|
596
|
+
name,
|
|
597
|
+
email,
|
|
598
|
+
date: unixToIso(Number(secs), tz)
|
|
599
|
+
};
|
|
600
|
+
}
|
|
601
|
+
function unixToIso(secs, tz) {
|
|
602
|
+
if (!Number.isFinite(secs)) return "";
|
|
603
|
+
const sign = tz.startsWith("-") ? -1 : 1;
|
|
604
|
+
const tzHours = Number(tz.slice(1, 3));
|
|
605
|
+
const tzMins = Number(tz.slice(3, 5));
|
|
606
|
+
const offsetMs = sign * (tzHours * 60 + tzMins) * 6e4;
|
|
607
|
+
const d = new Date(secs * 1e3 + offsetMs);
|
|
608
|
+
const pad = (n, w = 2) => String(n).padStart(w, "0");
|
|
609
|
+
return `${d.getUTCFullYear()}-${pad(d.getUTCMonth() + 1)}-${pad(d.getUTCDate())}T${pad(d.getUTCHours())}:${pad(d.getUTCMinutes())}:${pad(d.getUTCSeconds())}${tz.slice(0, 3)}:${tz.slice(3)}`;
|
|
610
|
+
}
|
|
611
|
+
function splitMessage(msgLines) {
|
|
612
|
+
if (msgLines.length === 0) return {
|
|
613
|
+
subject: "",
|
|
614
|
+
body: ""
|
|
615
|
+
};
|
|
616
|
+
let start = 0;
|
|
617
|
+
while (start < msgLines.length && msgLines[start] === "") start++;
|
|
618
|
+
let stop = msgLines.length;
|
|
619
|
+
while (stop > start && msgLines[stop - 1] === "") stop--;
|
|
620
|
+
if (start >= stop) return {
|
|
621
|
+
subject: "",
|
|
622
|
+
body: ""
|
|
623
|
+
};
|
|
624
|
+
const subject = msgLines[start];
|
|
625
|
+
let bodyStart = start + 1;
|
|
626
|
+
if (bodyStart < stop && msgLines[bodyStart] === "") bodyStart++;
|
|
627
|
+
return {
|
|
628
|
+
subject,
|
|
629
|
+
body: msgLines.slice(bodyStart, stop).join("\n")
|
|
630
|
+
};
|
|
631
|
+
}
|
|
632
|
+
async function getShowDiff(revspecs, pathspecs = []) {
|
|
633
|
+
if (revspecs.length === 0) return {
|
|
634
|
+
commits: [],
|
|
635
|
+
patch: "",
|
|
636
|
+
truncated: 0
|
|
637
|
+
};
|
|
638
|
+
let shaList;
|
|
639
|
+
try {
|
|
640
|
+
const { stdout } = await execFileAsync("git", [
|
|
641
|
+
"rev-list",
|
|
642
|
+
"--no-walk",
|
|
643
|
+
"--reverse",
|
|
644
|
+
...revspecs,
|
|
645
|
+
...pathspecs.length ? ["--", ...pathspecs] : []
|
|
646
|
+
], {
|
|
647
|
+
encoding: "utf-8",
|
|
648
|
+
maxBuffer: 50 * 1024 * 1024
|
|
649
|
+
});
|
|
650
|
+
shaList = stdout.split("\n").filter(Boolean);
|
|
651
|
+
} catch {
|
|
652
|
+
return {
|
|
653
|
+
commits: [],
|
|
654
|
+
patch: "",
|
|
655
|
+
truncated: 0
|
|
656
|
+
};
|
|
657
|
+
}
|
|
658
|
+
if (shaList.length === 0) return {
|
|
659
|
+
commits: [],
|
|
660
|
+
patch: "",
|
|
661
|
+
truncated: 0
|
|
662
|
+
};
|
|
663
|
+
const truncated = Math.max(0, shaList.length - 100);
|
|
664
|
+
const trimmed = truncated === 0 ? shaList : shaList.slice(0, 100);
|
|
665
|
+
let raw;
|
|
666
|
+
try {
|
|
667
|
+
const { stdout } = await execFileAsync("git", [
|
|
668
|
+
"log",
|
|
669
|
+
"--no-walk",
|
|
670
|
+
"--reverse",
|
|
671
|
+
"-p",
|
|
672
|
+
"--pretty=raw",
|
|
673
|
+
...DIFF_FLAGS,
|
|
674
|
+
...trimmed,
|
|
675
|
+
...pathspecs.length ? ["--", ...pathspecs] : []
|
|
676
|
+
], {
|
|
677
|
+
encoding: "utf-8",
|
|
678
|
+
maxBuffer: 200 * 1024 * 1024
|
|
679
|
+
});
|
|
680
|
+
raw = stdout;
|
|
681
|
+
} catch {
|
|
682
|
+
return {
|
|
683
|
+
commits: [],
|
|
684
|
+
patch: "",
|
|
685
|
+
truncated: 0
|
|
686
|
+
};
|
|
687
|
+
}
|
|
688
|
+
const commits = parseGitShowRaw(raw);
|
|
689
|
+
return {
|
|
690
|
+
commits,
|
|
691
|
+
patch: commits.map((c) => c.patch).filter(Boolean).join("\n"),
|
|
692
|
+
truncated
|
|
693
|
+
};
|
|
694
|
+
}
|
|
695
|
+
/**
|
|
696
|
+
* Cheap summary of a revspec series: just subject + author + date. Used by
|
|
697
|
+
* the diff overview banner to render "what am I looking at?" for custom
|
|
698
|
+
* non-show revisions (e.g. `diffing main..feature`).
|
|
699
|
+
*
|
|
700
|
+
* Uses `git log --no-walk --reverse --pretty=%s%x00%an%x00%aI`. The NUL
|
|
701
|
+
* separators avoid the `|` ambiguity the older `getHunkHistory` parser
|
|
702
|
+
* suffers from; we keep that call alone rather than refactoring it.
|
|
703
|
+
*
|
|
704
|
+
* `--no-walk` keeps single-SHA revspecs cheap (just that commit's metadata)
|
|
705
|
+
* while ranges still expand normally. Reuses the same `MAX_SHOW_COMMITS`
|
|
706
|
+
* cap as `getShowDiff` so the banner and the per-commit view agree.
|
|
707
|
+
*/
|
|
708
|
+
async function getCommitSeriesSummary(revspecs, pathspecs = []) {
|
|
709
|
+
const empty = {
|
|
710
|
+
commitCount: 0,
|
|
711
|
+
truncated: 0,
|
|
712
|
+
subjects: [],
|
|
713
|
+
authors: []
|
|
714
|
+
};
|
|
715
|
+
if (revspecs.length === 0) return empty;
|
|
716
|
+
let shaList;
|
|
717
|
+
try {
|
|
718
|
+
const { stdout } = await execFileAsync("git", [
|
|
719
|
+
"rev-list",
|
|
720
|
+
"--no-walk",
|
|
721
|
+
"--reverse",
|
|
722
|
+
...revspecs,
|
|
723
|
+
...pathspecs.length ? ["--", ...pathspecs] : []
|
|
724
|
+
], {
|
|
725
|
+
encoding: "utf-8",
|
|
726
|
+
maxBuffer: 50 * 1024 * 1024
|
|
727
|
+
});
|
|
728
|
+
shaList = stdout.split("\n").filter(Boolean);
|
|
729
|
+
} catch {
|
|
730
|
+
return empty;
|
|
731
|
+
}
|
|
732
|
+
const totalCommits = shaList.length;
|
|
733
|
+
if (totalCommits === 0) return empty;
|
|
734
|
+
const truncated = Math.max(0, totalCommits - 100);
|
|
735
|
+
const trimmed = truncated === 0 ? shaList : shaList.slice(0, 100);
|
|
736
|
+
let raw;
|
|
737
|
+
try {
|
|
738
|
+
const { stdout } = await execFileAsync("git", [
|
|
739
|
+
"log",
|
|
740
|
+
"--no-walk",
|
|
741
|
+
"--reverse",
|
|
742
|
+
"--pretty=%s%x00%an%x00%aI",
|
|
743
|
+
...DIFF_FLAGS,
|
|
744
|
+
...trimmed,
|
|
745
|
+
...pathspecs.length ? ["--", ...pathspecs] : []
|
|
746
|
+
], {
|
|
747
|
+
encoding: "utf-8",
|
|
748
|
+
maxBuffer: 50 * 1024 * 1024
|
|
749
|
+
});
|
|
750
|
+
raw = stdout;
|
|
751
|
+
} catch {
|
|
752
|
+
return {
|
|
753
|
+
...empty,
|
|
754
|
+
commitCount: totalCommits,
|
|
755
|
+
truncated
|
|
756
|
+
};
|
|
757
|
+
}
|
|
758
|
+
const subjects = [];
|
|
759
|
+
const authors = [];
|
|
760
|
+
const seenAuthors = /* @__PURE__ */ new Set();
|
|
761
|
+
let fromDate;
|
|
762
|
+
let toDate;
|
|
763
|
+
for (const line of raw.split("\n")) {
|
|
764
|
+
if (!line) continue;
|
|
765
|
+
const parts = line.split("\0");
|
|
766
|
+
const subject = parts[0] ?? "";
|
|
767
|
+
const author = parts[1] ?? "";
|
|
768
|
+
const date = parts[2] ?? "";
|
|
769
|
+
if (subject) subjects.push(subject);
|
|
770
|
+
if (author && !seenAuthors.has(author)) {
|
|
771
|
+
seenAuthors.add(author);
|
|
772
|
+
authors.push(author);
|
|
773
|
+
}
|
|
774
|
+
if (date) {
|
|
775
|
+
if (!fromDate || date < fromDate) fromDate = date;
|
|
776
|
+
if (!toDate || date > toDate) toDate = date;
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
return {
|
|
780
|
+
commitCount: totalCommits,
|
|
781
|
+
truncated,
|
|
782
|
+
subjects,
|
|
783
|
+
authors,
|
|
784
|
+
fromDate,
|
|
785
|
+
toDate
|
|
786
|
+
};
|
|
787
|
+
}
|
|
788
|
+
//#endregion
|
|
789
|
+
export { isSafePath as C, revertHunk as S, getUntrackedFilePathsAsync as _, getCustomGitDiffAsync as a, listRepoFiles as b, getGitDiffAsync as c, getProjectStorageDir as d, getRepoName as f, getTabSizeForFiles as g, getShowDiff as h, getCommitSeriesSummary as i, getHunkHistory as l, getRepoRootAsync as m, extractPatchHeader as n, getFileContent as o, getRepoRoot as p, getBranchName as r, getFilePatch as s, extractHunk as t, getMergeStatus as u, gitAddFile as v, toSafeRelativePath as w, parseGitShowRaw as x, isGitRepo as y };
|