trident-git 0.2.0 → 0.2.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.
Files changed (310) hide show
  1. package/.next/BUILD_ID +1 -0
  2. package/.next/app-path-routes-manifest.json +23 -0
  3. package/.next/build-manifest.json +20 -0
  4. package/.next/export-marker.json +6 -0
  5. package/.next/images-manifest.json +73 -0
  6. package/.next/next-minimal-server.js.nft.json +1 -0
  7. package/.next/next-server.js.nft.json +1 -0
  8. package/.next/package.json +1 -0
  9. package/.next/prerender-manifest.json +282 -0
  10. package/.next/react-loadable-manifest.json +1 -0
  11. package/.next/required-server-files.js +330 -0
  12. package/.next/required-server-files.json +330 -0
  13. package/.next/routes-manifest.json +170 -0
  14. package/.next/server/app/_global-error/page.js +3 -0
  15. package/.next/server/app/_global-error/page.js.nft.json +1 -0
  16. package/.next/server/app/_global-error/page_client-reference-manifest.js +1 -0
  17. package/.next/server/app/_global-error.html +2 -0
  18. package/.next/server/app/_global-error.meta +16 -0
  19. package/.next/server/app/_global-error.rsc +12 -0
  20. package/.next/server/app/_global-error.segments/_full.segment.rsc +12 -0
  21. package/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +5 -0
  22. package/.next/server/app/_global-error.segments/_global-error.segment.rsc +4 -0
  23. package/.next/server/app/_global-error.segments/_head.segment.rsc +5 -0
  24. package/.next/server/app/_global-error.segments/_index.segment.rsc +4 -0
  25. package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -0
  26. package/.next/server/app/_not-found/page.js +2 -0
  27. package/.next/server/app/_not-found/page.js.nft.json +1 -0
  28. package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -0
  29. package/.next/server/app/_not-found.html +1 -0
  30. package/.next/server/app/_not-found.meta +16 -0
  31. package/.next/server/app/_not-found.rsc +19 -0
  32. package/.next/server/app/_not-found.segments/_full.segment.rsc +19 -0
  33. package/.next/server/app/_not-found.segments/_head.segment.rsc +6 -0
  34. package/.next/server/app/_not-found.segments/_index.segment.rsc +8 -0
  35. package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +5 -0
  36. package/.next/server/app/_not-found.segments/_not-found.segment.rsc +4 -0
  37. package/.next/server/app/_not-found.segments/_tree.segment.rsc +6 -0
  38. package/.next/server/app/api/credentials/route.js +1 -0
  39. package/.next/server/app/api/credentials/route.js.nft.json +1 -0
  40. package/.next/server/app/api/credentials/route_client-reference-manifest.js +1 -0
  41. package/.next/server/app/api/custom-scripts/route.js +3 -0
  42. package/.next/server/app/api/custom-scripts/route.js.nft.json +1 -0
  43. package/.next/server/app/api/custom-scripts/route_client-reference-manifest.js +1 -0
  44. package/.next/server/app/api/fs/route.js +1 -0
  45. package/.next/server/app/api/fs/route.js.nft.json +1 -0
  46. package/.next/server/app/api/fs/route_client-reference-manifest.js +1 -0
  47. package/.next/server/app/api/git/action/route.js +1 -0
  48. package/.next/server/app/api/git/action/route.js.nft.json +1 -0
  49. package/.next/server/app/api/git/action/route_client-reference-manifest.js +1 -0
  50. package/.next/server/app/api/git/branches/route.js +1 -0
  51. package/.next/server/app/api/git/branches/route.js.nft.json +1 -0
  52. package/.next/server/app/api/git/branches/route_client-reference-manifest.js +1 -0
  53. package/.next/server/app/api/git/diff/route.js +1 -0
  54. package/.next/server/app/api/git/diff/route.js.nft.json +1 -0
  55. package/.next/server/app/api/git/diff/route_client-reference-manifest.js +1 -0
  56. package/.next/server/app/api/git/log/route.js +1 -0
  57. package/.next/server/app/api/git/log/route.js.nft.json +1 -0
  58. package/.next/server/app/api/git/log/route_client-reference-manifest.js +1 -0
  59. package/.next/server/app/api/git/status/route.js +1 -0
  60. package/.next/server/app/api/git/status/route.js.nft.json +1 -0
  61. package/.next/server/app/api/git/status/route_client-reference-manifest.js +1 -0
  62. package/.next/server/app/api/repos/route.js +1 -0
  63. package/.next/server/app/api/repos/route.js.nft.json +1 -0
  64. package/.next/server/app/api/repos/route_client-reference-manifest.js +1 -0
  65. package/.next/server/app/api/settings/route.js +1 -0
  66. package/.next/server/app/api/settings/route.js.nft.json +1 -0
  67. package/.next/server/app/api/settings/route_client-reference-manifest.js +1 -0
  68. package/.next/server/app/credentials/page.js +4 -0
  69. package/.next/server/app/credentials/page.js.nft.json +1 -0
  70. package/.next/server/app/credentials/page_client-reference-manifest.js +1 -0
  71. package/.next/server/app/credentials.html +1 -0
  72. package/.next/server/app/credentials.meta +15 -0
  73. package/.next/server/app/credentials.rsc +23 -0
  74. package/.next/server/app/credentials.segments/_full.segment.rsc +23 -0
  75. package/.next/server/app/credentials.segments/_head.segment.rsc +6 -0
  76. package/.next/server/app/credentials.segments/_index.segment.rsc +8 -0
  77. package/.next/server/app/credentials.segments/_tree.segment.rsc +6 -0
  78. package/.next/server/app/credentials.segments/credentials/__PAGE__.segment.rsc +9 -0
  79. package/.next/server/app/credentials.segments/credentials.segment.rsc +4 -0
  80. package/.next/server/app/icon.png/route.js +1 -0
  81. package/.next/server/app/icon.png/route.js.nft.json +1 -0
  82. package/.next/server/app/icon.png.meta +1 -0
  83. package/.next/server/app/index.html +1 -0
  84. package/.next/server/app/index.meta +14 -0
  85. package/.next/server/app/index.rsc +20 -0
  86. package/.next/server/app/index.segments/__PAGE__.segment.rsc +6 -0
  87. package/.next/server/app/index.segments/_full.segment.rsc +20 -0
  88. package/.next/server/app/index.segments/_head.segment.rsc +6 -0
  89. package/.next/server/app/index.segments/_index.segment.rsc +8 -0
  90. package/.next/server/app/index.segments/_tree.segment.rsc +6 -0
  91. package/.next/server/app/page.js +2 -0
  92. package/.next/server/app/page.js.nft.json +1 -0
  93. package/.next/server/app/page_client-reference-manifest.js +1 -0
  94. package/.next/server/app/workspace/changes/page.js +4 -0
  95. package/.next/server/app/workspace/changes/page.js.nft.json +1 -0
  96. package/.next/server/app/workspace/changes/page_client-reference-manifest.js +1 -0
  97. package/.next/server/app/workspace/changes.html +1 -0
  98. package/.next/server/app/workspace/changes.meta +16 -0
  99. package/.next/server/app/workspace/changes.rsc +26 -0
  100. package/.next/server/app/workspace/changes.segments/_full.segment.rsc +26 -0
  101. package/.next/server/app/workspace/changes.segments/_head.segment.rsc +6 -0
  102. package/.next/server/app/workspace/changes.segments/_index.segment.rsc +8 -0
  103. package/.next/server/app/workspace/changes.segments/_tree.segment.rsc +7 -0
  104. package/.next/server/app/workspace/changes.segments/workspace/changes/__PAGE__.segment.rsc +10 -0
  105. package/.next/server/app/workspace/changes.segments/workspace/changes.segment.rsc +4 -0
  106. package/.next/server/app/workspace/changes.segments/workspace.segment.rsc +7 -0
  107. package/.next/server/app/workspace/custom-scripts/page.js +2 -0
  108. package/.next/server/app/workspace/custom-scripts/page.js.nft.json +1 -0
  109. package/.next/server/app/workspace/custom-scripts/page_client-reference-manifest.js +1 -0
  110. package/.next/server/app/workspace/custom-scripts.html +1 -0
  111. package/.next/server/app/workspace/custom-scripts.meta +16 -0
  112. package/.next/server/app/workspace/custom-scripts.rsc +25 -0
  113. package/.next/server/app/workspace/custom-scripts.segments/_full.segment.rsc +25 -0
  114. package/.next/server/app/workspace/custom-scripts.segments/_head.segment.rsc +6 -0
  115. package/.next/server/app/workspace/custom-scripts.segments/_index.segment.rsc +8 -0
  116. package/.next/server/app/workspace/custom-scripts.segments/_tree.segment.rsc +6 -0
  117. package/.next/server/app/workspace/custom-scripts.segments/workspace/custom-scripts/__PAGE__.segment.rsc +9 -0
  118. package/.next/server/app/workspace/custom-scripts.segments/workspace/custom-scripts.segment.rsc +4 -0
  119. package/.next/server/app/workspace/custom-scripts.segments/workspace.segment.rsc +7 -0
  120. package/.next/server/app/workspace/history/page.js +2 -0
  121. package/.next/server/app/workspace/history/page.js.nft.json +1 -0
  122. package/.next/server/app/workspace/history/page_client-reference-manifest.js +1 -0
  123. package/.next/server/app/workspace/history.html +1 -0
  124. package/.next/server/app/workspace/history.meta +16 -0
  125. package/.next/server/app/workspace/history.rsc +26 -0
  126. package/.next/server/app/workspace/history.segments/_full.segment.rsc +26 -0
  127. package/.next/server/app/workspace/history.segments/_head.segment.rsc +6 -0
  128. package/.next/server/app/workspace/history.segments/_index.segment.rsc +8 -0
  129. package/.next/server/app/workspace/history.segments/_tree.segment.rsc +7 -0
  130. package/.next/server/app/workspace/history.segments/workspace/history/__PAGE__.segment.rsc +10 -0
  131. package/.next/server/app/workspace/history.segments/workspace/history.segment.rsc +4 -0
  132. package/.next/server/app/workspace/history.segments/workspace.segment.rsc +7 -0
  133. package/.next/server/app/workspace/page.js +2 -0
  134. package/.next/server/app/workspace/page.js.nft.json +1 -0
  135. package/.next/server/app/workspace/page_client-reference-manifest.js +1 -0
  136. package/.next/server/app/workspace/settings/page.js +2 -0
  137. package/.next/server/app/workspace/settings/page.js.nft.json +1 -0
  138. package/.next/server/app/workspace/settings/page_client-reference-manifest.js +1 -0
  139. package/.next/server/app/workspace/settings.html +1 -0
  140. package/.next/server/app/workspace/settings.meta +16 -0
  141. package/.next/server/app/workspace/settings.rsc +25 -0
  142. package/.next/server/app/workspace/settings.segments/_full.segment.rsc +25 -0
  143. package/.next/server/app/workspace/settings.segments/_head.segment.rsc +6 -0
  144. package/.next/server/app/workspace/settings.segments/_index.segment.rsc +8 -0
  145. package/.next/server/app/workspace/settings.segments/_tree.segment.rsc +6 -0
  146. package/.next/server/app/workspace/settings.segments/workspace/settings/__PAGE__.segment.rsc +9 -0
  147. package/.next/server/app/workspace/settings.segments/workspace/settings.segment.rsc +4 -0
  148. package/.next/server/app/workspace/settings.segments/workspace.segment.rsc +7 -0
  149. package/.next/server/app/workspace/stashes/page.js +2 -0
  150. package/.next/server/app/workspace/stashes/page.js.nft.json +1 -0
  151. package/.next/server/app/workspace/stashes/page_client-reference-manifest.js +1 -0
  152. package/.next/server/app/workspace/stashes.html +1 -0
  153. package/.next/server/app/workspace/stashes.meta +16 -0
  154. package/.next/server/app/workspace/stashes.rsc +26 -0
  155. package/.next/server/app/workspace/stashes.segments/_full.segment.rsc +26 -0
  156. package/.next/server/app/workspace/stashes.segments/_head.segment.rsc +6 -0
  157. package/.next/server/app/workspace/stashes.segments/_index.segment.rsc +8 -0
  158. package/.next/server/app/workspace/stashes.segments/_tree.segment.rsc +7 -0
  159. package/.next/server/app/workspace/stashes.segments/workspace/stashes/__PAGE__.segment.rsc +10 -0
  160. package/.next/server/app/workspace/stashes.segments/workspace/stashes.segment.rsc +4 -0
  161. package/.next/server/app/workspace/stashes.segments/workspace.segment.rsc +7 -0
  162. package/.next/server/app/workspace.html +1 -0
  163. package/.next/server/app/workspace.meta +15 -0
  164. package/.next/server/app/workspace.rsc +26 -0
  165. package/.next/server/app/workspace.segments/_full.segment.rsc +26 -0
  166. package/.next/server/app/workspace.segments/_head.segment.rsc +6 -0
  167. package/.next/server/app/workspace.segments/_index.segment.rsc +8 -0
  168. package/.next/server/app/workspace.segments/_tree.segment.rsc +7 -0
  169. package/.next/server/app/workspace.segments/workspace/__PAGE__.segment.rsc +10 -0
  170. package/.next/server/app/workspace.segments/workspace.segment.rsc +7 -0
  171. package/.next/server/app-paths-manifest.json +23 -0
  172. package/.next/server/chunks/116.js +1 -0
  173. package/.next/server/chunks/170.js +1 -0
  174. package/.next/server/chunks/309.js +6 -0
  175. package/.next/server/chunks/356.js +1 -0
  176. package/.next/server/chunks/392.js +1 -0
  177. package/.next/server/chunks/443.js +37 -0
  178. package/.next/server/chunks/445.js +22 -0
  179. package/.next/server/chunks/471.js +13 -0
  180. package/.next/server/chunks/557.js +5 -0
  181. package/.next/server/chunks/680.js +1 -0
  182. package/.next/server/chunks/739.js +5 -0
  183. package/.next/server/chunks/781.js +1 -0
  184. package/.next/server/chunks/813.js +1 -0
  185. package/.next/server/chunks/816.js +13 -0
  186. package/.next/server/functions-config-manifest.json +6 -0
  187. package/.next/server/interception-route-rewrite-manifest.js +1 -0
  188. package/.next/server/middleware-build-manifest.js +1 -0
  189. package/.next/server/middleware-manifest.json +6 -0
  190. package/.next/server/middleware-react-loadable-manifest.js +1 -0
  191. package/.next/server/next-font-manifest.js +1 -0
  192. package/.next/server/next-font-manifest.json +1 -0
  193. package/.next/server/pages/404.html +1 -0
  194. package/.next/server/pages/500.html +2 -0
  195. package/.next/server/pages-manifest.json +4 -0
  196. package/.next/server/server-reference-manifest.js +1 -0
  197. package/.next/server/server-reference-manifest.json +1 -0
  198. package/.next/server/webpack-runtime.js +1 -0
  199. package/.next/static/chunks/131-6237c77d708182b1.js +1 -0
  200. package/.next/static/chunks/147-d4cb3e03218d75df.js +6 -0
  201. package/.next/static/chunks/176-b068120641913226.js +1 -0
  202. package/.next/static/chunks/200-3664e65687e716a2.js +1 -0
  203. package/.next/static/chunks/26-d93e82c9c09ff972.js +1 -0
  204. package/.next/static/chunks/4bd1b696-096d35a2bd1da3af.js +1 -0
  205. package/.next/static/chunks/500-5d45efbf86b8d36e.js +1 -0
  206. package/.next/static/chunks/772-b39107b40c31b6b6.js +3 -0
  207. package/.next/static/chunks/785-e6d755147ff87934.js +1 -0
  208. package/.next/static/chunks/790-3e8fa6e595d97940.js +6 -0
  209. package/.next/static/chunks/794-37dad9bbc14b04b8.js +2 -0
  210. package/.next/static/chunks/841-4d865378216d36eb.js +13 -0
  211. package/.next/static/chunks/app/_global-error/page-9eb8a19109bbd711.js +1 -0
  212. package/.next/static/chunks/app/_not-found/page-5bea07380837254f.js +1 -0
  213. package/.next/static/chunks/app/api/credentials/route-9eb8a19109bbd711.js +1 -0
  214. package/.next/static/chunks/app/api/custom-scripts/route-9eb8a19109bbd711.js +1 -0
  215. package/.next/static/chunks/app/api/fs/route-9eb8a19109bbd711.js +1 -0
  216. package/.next/static/chunks/app/api/git/action/route-9eb8a19109bbd711.js +1 -0
  217. package/.next/static/chunks/app/api/git/branches/route-9eb8a19109bbd711.js +1 -0
  218. package/.next/static/chunks/app/api/git/diff/route-9eb8a19109bbd711.js +1 -0
  219. package/.next/static/chunks/app/api/git/log/route-9eb8a19109bbd711.js +1 -0
  220. package/.next/static/chunks/app/api/git/status/route-9eb8a19109bbd711.js +1 -0
  221. package/.next/static/chunks/app/api/repos/route-9eb8a19109bbd711.js +1 -0
  222. package/.next/static/chunks/app/api/settings/route-9eb8a19109bbd711.js +1 -0
  223. package/.next/static/chunks/app/credentials/page-23c1807ad9f68a40.js +1 -0
  224. package/.next/static/chunks/app/layout-a06241c385e50ae0.js +1 -0
  225. package/.next/static/chunks/app/page-c8404790f5191fd7.js +1 -0
  226. package/.next/static/chunks/app/workspace/changes/page-8cca17f80e66f534.js +3 -0
  227. package/.next/static/chunks/app/workspace/custom-scripts/page-ee24b9dd7779244f.js +1 -0
  228. package/.next/static/chunks/app/workspace/history/page-b7017fea285c804c.js +1 -0
  229. package/.next/static/chunks/app/workspace/layout-cb86c00fdcd8db08.js +1 -0
  230. package/.next/static/chunks/app/workspace/page-21211acc90c08509.js +1 -0
  231. package/.next/static/chunks/app/workspace/settings/page-7ccb58ece31492e9.js +1 -0
  232. package/.next/static/chunks/app/workspace/stashes/page-0e1b8304c4b0f4f3.js +1 -0
  233. package/.next/static/chunks/framework-75892d61b920805f.js +1 -0
  234. package/.next/static/chunks/main-app-221e1b945693a142.js +1 -0
  235. package/.next/static/chunks/main-f1d55fffd3cc9809.js +5 -0
  236. package/.next/static/chunks/next/dist/client/components/builtin/app-error-9eb8a19109bbd711.js +1 -0
  237. package/.next/static/chunks/next/dist/client/components/builtin/forbidden-9eb8a19109bbd711.js +1 -0
  238. package/.next/static/chunks/next/dist/client/components/builtin/global-error-f2a2c324f3740ed8.js +1 -0
  239. package/.next/static/chunks/next/dist/client/components/builtin/not-found-9eb8a19109bbd711.js +1 -0
  240. package/.next/static/chunks/next/dist/client/components/builtin/unauthorized-9eb8a19109bbd711.js +1 -0
  241. package/.next/static/chunks/polyfills-42372ed130431b0a.js +1 -0
  242. package/.next/static/chunks/webpack-7dec27dc3bfe207a.js +1 -0
  243. package/.next/static/css/36968b90b2339533.css +1 -0
  244. package/.next/static/css/c56c0e57397a3a60.css +1 -0
  245. package/.next/static/css/de70bee13400563f.css +1 -0
  246. package/.next/static/i5Fy6fyWcq44ejS8h_y1c/_buildManifest.js +1 -0
  247. package/.next/static/i5Fy6fyWcq44ejS8h_y1c/_ssgManifest.js +1 -0
  248. package/.next/static/media/4cf2300e9c8272f7-s.p.woff2 +0 -0
  249. package/.next/static/media/747892c23ea88013-s.woff2 +0 -0
  250. package/.next/static/media/8d697b304b401681-s.woff2 +0 -0
  251. package/.next/static/media/93f479601ee12b01-s.p.woff2 +0 -0
  252. package/.next/static/media/9610d9e46709d722-s.woff2 +0 -0
  253. package/.next/static/media/ba015fad6dcf6784-s.woff2 +0 -0
  254. package/README.md +2 -0
  255. package/bin/trident-git.mjs +9 -11
  256. package/package.json +7 -5
  257. package/eslint.config.mjs +0 -18
  258. package/postcss.config.mjs +0 -7
  259. package/src/app/api/credentials/route.ts +0 -113
  260. package/src/app/api/custom-scripts/route.ts +0 -203
  261. package/src/app/api/fs/route.ts +0 -75
  262. package/src/app/api/git/action/route.ts +0 -383
  263. package/src/app/api/git/branches/route.ts +0 -20
  264. package/src/app/api/git/diff/route.ts +0 -104
  265. package/src/app/api/git/log/route.ts +0 -28
  266. package/src/app/api/git/status/route.ts +0 -28
  267. package/src/app/api/repos/route.ts +0 -84
  268. package/src/app/api/settings/route.ts +0 -37
  269. package/src/app/credentials/page.tsx +0 -408
  270. package/src/app/globals.css +0 -109
  271. package/src/app/layout.tsx +0 -38
  272. package/src/app/page.tsx +0 -10
  273. package/src/app/providers.tsx +0 -21
  274. package/src/app/workspace/changes/page.tsx +0 -27
  275. package/src/app/workspace/custom-scripts/page.tsx +0 -247
  276. package/src/app/workspace/history/page.tsx +0 -27
  277. package/src/app/workspace/layout.tsx +0 -26
  278. package/src/app/workspace/page.tsx +0 -27
  279. package/src/app/workspace/settings/page.tsx +0 -233
  280. package/src/app/workspace/stashes/page.tsx +0 -395
  281. package/src/components/command-palette.tsx +0 -178
  282. package/src/components/context-menu.tsx +0 -200
  283. package/src/components/fs-browser.tsx +0 -154
  284. package/src/components/git/diff-view.tsx +0 -137
  285. package/src/components/git/git-graph.tsx +0 -489
  286. package/src/components/git/grouped-diff-viewer.tsx +0 -332
  287. package/src/components/git/history-view.tsx +0 -4862
  288. package/src/components/git/image-diff-view.tsx +0 -342
  289. package/src/components/git/status-view.tsx +0 -597
  290. package/src/components/home-settings-modal.tsx +0 -192
  291. package/src/components/layout/sidebar.tsx +0 -256
  292. package/src/components/repo-list.tsx +0 -206
  293. package/src/components/theme-toggle.tsx +0 -37
  294. package/src/components/toaster.tsx +0 -36
  295. package/src/components/workspace-repo-open-tracker.tsx +0 -39
  296. package/src/hooks/use-credentials.ts +0 -123
  297. package/src/hooks/use-escape-dismiss.ts +0 -72
  298. package/src/hooks/use-git.ts +0 -448
  299. package/src/hooks/use-toast.ts +0 -280
  300. package/src/hooks/use-workspace-title.ts +0 -23
  301. package/src/lib/api-utils.ts +0 -24
  302. package/src/lib/branch-colors.ts +0 -98
  303. package/src/lib/credentials.ts +0 -404
  304. package/src/lib/git.ts +0 -1510
  305. package/src/lib/graph-utils.ts +0 -253
  306. package/src/lib/store.ts +0 -145
  307. package/src/lib/types.ts +0 -95
  308. package/src/lib/utils.ts +0 -266
  309. package/tsconfig.json +0 -34
  310. /package/{src/app/icon.png → .next/server/app/icon.png.body} +0 -0
@@ -1,489 +0,0 @@
1
- 'use client';
2
-
3
- import { useMemo, useRef, useState, useImperativeHandle, forwardRef, useEffect, useCallback } from 'react';
4
- import { Commit, BranchTrackingInfo } from '@/lib/types';
5
- import { generateGraphData } from '@/lib/graph-utils';
6
- import { cn } from '@/lib/utils';
7
- import { ContextMenu, ContextMenuItem } from '@/components/context-menu';
8
- import { getBranchTagColors } from '@/lib/branch-colors';
9
-
10
-
11
- const ROW_HEIGHT = 24; // Compact rows like Fork
12
- const LANE_WIDTH = 12;
13
- const DOT_SIZE = 3;
14
- const STROKE_WIDTH = 2;
15
-
16
- // Helper function to highlight matching text
17
- function HighlightedText({ text, searchQuery }: { text: string; searchQuery: string }) {
18
- if (!searchQuery || !text) return <>{text}</>;
19
-
20
- const query = searchQuery.toLowerCase();
21
- const lowerText = text.toLowerCase();
22
- const parts: { text: string; highlighted: boolean }[] = [];
23
-
24
- let lastIndex = 0;
25
- let index = lowerText.indexOf(query);
26
-
27
- while (index !== -1) {
28
- // Add non-matching part
29
- if (index > lastIndex) {
30
- parts.push({ text: text.slice(lastIndex, index), highlighted: false });
31
- }
32
- // Add matching part (preserve original case)
33
- parts.push({ text: text.slice(index, index + query.length), highlighted: true });
34
- lastIndex = index + query.length;
35
- index = lowerText.indexOf(query, lastIndex);
36
- }
37
-
38
- // Add remaining non-matching part
39
- if (lastIndex < text.length) {
40
- parts.push({ text: text.slice(lastIndex), highlighted: false });
41
- }
42
-
43
- if (parts.length === 0) return <>{text}</>;
44
-
45
- return (
46
- <>
47
- {parts.map((part, i) =>
48
- part.highlighted ? (
49
- <mark key={i} className="bg-warning text-warning-content rounded-sm px-0.5">{part.text}</mark>
50
- ) : (
51
- <span key={i}>{part.text}</span>
52
- )
53
- )}
54
- </>
55
- );
56
- }
57
-
58
- export interface GitGraphHandle {
59
- scrollToCommit: (hash: string) => boolean;
60
- }
61
-
62
- export const GitGraph = forwardRef<GitGraphHandle, {
63
- commits: Commit[],
64
- onSelectCommit?: (hash: string, modifiers?: { isMultiSelect: boolean; isRangeSelect: boolean }) => void,
65
- onResetToCommit?: (hash: string) => void,
66
- onRevertCommit?: (hash: string, message: string) => void,
67
- onCreateTag?: (hash: string) => void,
68
- onCherryPickCommit?: (hash: string, message: string) => void,
69
- onCherryPickSelectedCommits?: () => void,
70
- onRewordCommit?: (hash: string, subject: string, body: string, branch: string) => void,
71
- selectedHash?: string,
72
- selectedHashes?: Set<string>,
73
- onEndReached?: () => void,
74
- isLoadingMore?: boolean,
75
- currentBranch?: string,
76
- hiddenBranches?: Set<string>,
77
- localBranches?: string[],
78
- trackingInfo?: Record<string, BranchTrackingInfo>,
79
- getBranchTagContextMenuItems?: (displayRef: string) => ContextMenuItem[] | null
80
- }>(function GitGraph({
81
- commits,
82
- onSelectCommit,
83
- onResetToCommit,
84
- onRevertCommit,
85
- onCreateTag,
86
- onCherryPickCommit,
87
- onCherryPickSelectedCommits,
88
- onRewordCommit,
89
- selectedHash,
90
- selectedHashes,
91
- onEndReached,
92
- isLoadingMore,
93
- currentBranch,
94
- hiddenBranches,
95
- localBranches = [],
96
- trackingInfo,
97
- getBranchTagContextMenuItems
98
- }, ref) {
99
- const normalizeDecoratedRef = useCallback((ref: string) => {
100
- if (ref.startsWith('refs/heads/')) return ref.slice('refs/heads/'.length);
101
- if (ref.startsWith('refs/remotes/')) return ref.slice('refs/remotes/'.length);
102
- if (ref.startsWith('remotes/')) return ref.slice('remotes/'.length);
103
- return ref;
104
- }, []);
105
- const getRefDisplayName = useCallback((ref: string) => {
106
- if (ref.startsWith('tag:')) return ref.replace(/^tag:\s*/, '').trim();
107
- return ref;
108
- }, []);
109
-
110
- const nodes = useMemo(
111
- () => generateGraphData(commits, { localBranches }),
112
- [commits, localBranches]
113
- );
114
- const scrollRef = useRef<HTMLDivElement>(null);
115
- const searchInputRef = useRef<HTMLInputElement>(null);
116
-
117
- // Search state
118
- const [isSearchOpen, setIsSearchOpen] = useState(false);
119
- const [searchQuery, setSearchQuery] = useState('');
120
-
121
- // Handle Cmd+F to open search
122
- useEffect(() => {
123
- const handleKeyDown = (e: KeyboardEvent) => {
124
- // Cmd+F (Mac) or Ctrl+F (Windows/Linux)
125
- if ((e.metaKey || e.ctrlKey) && e.key === 'f') {
126
- e.preventDefault();
127
- setIsSearchOpen(true);
128
- // Focus input after render
129
- setTimeout(() => searchInputRef.current?.focus(), 0);
130
- }
131
- // Escape to close search
132
- if (e.key === 'Escape' && isSearchOpen) {
133
- setIsSearchOpen(false);
134
- setSearchQuery('');
135
- }
136
- };
137
-
138
- window.addEventListener('keydown', handleKeyDown);
139
- return () => window.removeEventListener('keydown', handleKeyDown);
140
- }, [isSearchOpen]);
141
-
142
- const handleCloseSearch = useCallback(() => {
143
- setIsSearchOpen(false);
144
- setSearchQuery('');
145
- }, []);
146
-
147
- // Expose scrollToCommit function via ref
148
- useImperativeHandle(ref, () => ({
149
- scrollToCommit: (hash: string) => {
150
- if (!nodes || nodes.length === 0) return false;
151
-
152
- const index = nodes.findIndex(n => n.hash === hash);
153
- if (index === -1) return false;
154
-
155
- // Scroll to the commit row
156
- if (scrollRef.current) {
157
- const scrollTop = index * ROW_HEIGHT - (scrollRef.current.clientHeight / 2) + ROW_HEIGHT / 2;
158
- scrollRef.current.scrollTop = Math.max(0, scrollTop);
159
- }
160
- return true;
161
- }
162
- }), [nodes]);
163
-
164
- if (!nodes || nodes.length === 0) return null;
165
-
166
- // Calculate SVG dimensions
167
- const maxLane = Math.max(...nodes.map(n => n.x), 0);
168
- const width = (maxLane + 1) * LANE_WIDTH + 20;
169
- const height = nodes.length * ROW_HEIGHT;
170
-
171
- const handleScroll = (e: React.UIEvent<HTMLDivElement>) => {
172
- const { scrollTop, clientHeight, scrollHeight } = e.currentTarget;
173
- if (scrollHeight - scrollTop - clientHeight < 100) {
174
- onEndReached?.();
175
- }
176
- };
177
-
178
- return (
179
- <div className="flex flex-col h-full bg-base-100 overflow-hidden font-mono text-sm select-none">
180
- {/* Search Input - Sticky on top */}
181
- {isSearchOpen && (
182
- <div className="sticky top-0 z-30 bg-base-100 border-b border-base-300 px-2 py-2 flex items-center gap-2">
183
- <span className="opacity-50">🔍</span>
184
- <input
185
- ref={searchInputRef}
186
- type="text"
187
- placeholder="Search in commits..."
188
- value={searchQuery}
189
- onChange={(e) => setSearchQuery(e.target.value)}
190
- className="input input-bordered input-sm flex-1 text-sm"
191
- autoFocus
192
- />
193
- <div className="tooltip tooltip-left z-50" data-tip="Close search (Esc)">
194
- <button
195
- onClick={handleCloseSearch}
196
- className="btn btn-ghost btn-sm btn-square"
197
- >
198
- ✖️
199
- </button>
200
- </div>
201
- </div>
202
- )}
203
-
204
- <div className="flex-1 overflow-auto h-full max-w-full px-2" onScroll={handleScroll} ref={scrollRef}>
205
- <div className="relative min-w-full" style={{ height }}>
206
- {/* SVG Graph Layout */}
207
- <svg width={width} height={height} className="absolute top-0 left-0 pointer-events-none z-10">
208
- {nodes.map((node) => (
209
- <g key={node.hash}>
210
- {/* Draw paths */}
211
- {node.paths.map((path, i) => {
212
- const x1 = path.x1 * LANE_WIDTH + LANE_WIDTH / 2;
213
- const y1 = path.y1 * ROW_HEIGHT + ROW_HEIGHT / 2;
214
- const x2 = path.x2 * LANE_WIDTH + LANE_WIDTH / 2;
215
- const y2 = path.y2 * ROW_HEIGHT + ROW_HEIGHT / 2;
216
-
217
- let d = '';
218
- if (path.type === 'straight') {
219
- d = `M ${x1} ${y1} L ${x2} ${y2}`;
220
- } else {
221
- // Fork/Merge styled Bezier
222
- // Standard cubic bezier: ctrl points at mid-y
223
- const cy1 = y1 + ROW_HEIGHT * 0.5;
224
- const cy2 = y2 - ROW_HEIGHT * 0.5;
225
- d = `M ${x1} ${y1} C ${x1} ${cy1}, ${x2} ${cy2}, ${x2} ${y2}`;
226
- }
227
-
228
- return (
229
- <path
230
- key={i}
231
- d={d}
232
- stroke={path.color}
233
- strokeWidth={STROKE_WIDTH}
234
- fill="none"
235
- strokeLinecap="round"
236
- />
237
- )
238
- })}
239
- </g>
240
- ))}
241
-
242
- {/* Draw Nodes on top of all paths to avoid overlap ugliness */}
243
- {nodes.map((node) => (
244
- <circle
245
- key={`dot-${node.hash}`}
246
- cx={node.x * LANE_WIDTH + LANE_WIDTH / 2}
247
- cy={node.y * ROW_HEIGHT + ROW_HEIGHT / 2}
248
- r={DOT_SIZE}
249
- fill={node.color}
250
- stroke={node.color}
251
- strokeWidth={STROKE_WIDTH}
252
- />
253
- ))}
254
- </svg>
255
-
256
- {/* List Rows */}
257
- <div style={{ width: '100%' }}>
258
- {nodes.map((node) => {
259
- const isSelected = selectedHashes ? selectedHashes.has(node.hash) : selectedHash === node.hash;
260
- const selectedCount = selectedHashes?.size ?? (selectedHash ? 1 : 0);
261
- const menuItems = [
262
- { label: "Reset to here", onClick: () => onResetToCommit?.(node.hash) },
263
- ];
264
- if (onRevertCommit) {
265
- menuItems.push({
266
- label: "Revert commit",
267
- onClick: () => onRevertCommit(node.hash, node.message),
268
- });
269
- }
270
- if (onCreateTag) {
271
- menuItems.push({
272
- label: "Create tag",
273
- onClick: () => onCreateTag(node.hash),
274
- });
275
- }
276
- if (onCherryPickCommit) {
277
- menuItems.push({
278
- label: "Cherry-pick commit",
279
- onClick: () => onCherryPickCommit(node.hash, node.message),
280
- });
281
- }
282
- if (onCherryPickSelectedCommits && selectedCount > 1 && isSelected) {
283
- menuItems.push({
284
- label: `Cherry-pick ${selectedCount} selected commits`,
285
- onClick: onCherryPickSelectedCommits,
286
- });
287
- }
288
-
289
- if (onRewordCommit && localBranches && localBranches.length > 0) {
290
- // Clean up refs: remove parentheses and split
291
- const refs = node.refs ? node.refs.replace(/[()]/g, '').split(',').map(r => r.trim()) : [];
292
- let targetBranch: string | null = null;
293
-
294
- for (const ref of refs) {
295
- // Handle "HEAD -> branch" format
296
- const cleanRef = ref.replace(/^HEAD\s*->\s*/, '');
297
-
298
- // Check if it is in localBranches
299
- if (localBranches.includes(cleanRef)) {
300
- targetBranch = cleanRef;
301
- // Prioritize current branch if found
302
- if (currentBranch && cleanRef === currentBranch) {
303
- break;
304
- }
305
- }
306
- }
307
-
308
- if (targetBranch) {
309
- menuItems.push({
310
- label: "Reword commit",
311
- onClick: () => onRewordCommit(node.hash, node.message, node.body ?? '', targetBranch!),
312
- });
313
- }
314
- }
315
-
316
-
317
- // Process refs to combine local and tracking remote branches
318
- const processRefs = () => {
319
- if (!node.refs) return [];
320
-
321
- const rawRefs = node.refs.replace(/^\s*\((.*)\)\s*$/, '$1').split(',').map(r => {
322
- const isHead = r.startsWith('HEAD -> ');
323
- const name = normalizeDecoratedRef(r.replace(/^HEAD\s*->\s*/, '').trim());
324
- return { raw: r, name, isHead };
325
- });
326
-
327
- const result: {
328
- displayName: string;
329
- primaryRef: string;
330
- secondaryRef?: string;
331
- isHead: boolean
332
- }[] = [];
333
-
334
- const processedIndices = new Set<number>();
335
- const isHidden = (name: string) => hiddenBranches && (hiddenBranches.has(name) || hiddenBranches.has(`remotes/${name}`));
336
-
337
- // First pass: find local branches and their tracking remotes
338
- rawRefs.forEach((ref, idx) => {
339
- if (processedIndices.has(idx)) return;
340
-
341
- // Only attempt to combine if local branch is visible
342
- if (localBranches.includes(ref.name) && !isHidden(ref.name)) {
343
- const tracking = trackingInfo?.[ref.name];
344
- if (tracking && tracking.upstream) {
345
- const normalizedUpstream = normalizeDecoratedRef(tracking.upstream.trim());
346
- const upstreamCandidates = new Set([
347
- normalizedUpstream,
348
- normalizeDecoratedRef(`remotes/${normalizedUpstream}`),
349
- normalizeDecoratedRef(`refs/remotes/${normalizedUpstream}`),
350
- ]);
351
- const upstreamIdx = rawRefs.findIndex(
352
- (r, i) => i !== idx && !processedIndices.has(i) && upstreamCandidates.has(r.name)
353
- );
354
-
355
- if (upstreamIdx !== -1) {
356
- const upstreamRef = rawRefs[upstreamIdx];
357
- const parts = normalizedUpstream.split('/');
358
- const remoteName = parts[0];
359
-
360
- result.push({
361
- displayName: `${ref.name} (${remoteName})`,
362
- primaryRef: ref.name,
363
- secondaryRef: upstreamRef.name,
364
- isHead: ref.isHead
365
- });
366
-
367
- processedIndices.add(idx);
368
- processedIndices.add(upstreamIdx);
369
- return;
370
- }
371
- }
372
- }
373
- });
374
-
375
- // Second pass: add remaining refs
376
- rawRefs.forEach((ref, idx) => {
377
- if (!processedIndices.has(idx)) {
378
- // Skip hidden branches
379
- if (isHidden(ref.name)) return;
380
-
381
- result.push({
382
- displayName: getRefDisplayName(ref.name),
383
- primaryRef: ref.name,
384
- isHead: ref.isHead
385
- });
386
- }
387
- });
388
-
389
- return result;
390
- };
391
-
392
- const processedTags = processRefs();
393
-
394
- return (
395
- <ContextMenu key={node.hash} items={menuItems}>
396
- <div
397
- className={cn(
398
- "flex items-center hover:bg-base-200 border-b border-base-200 last:border-0 cursor-pointer transition-colors text-xs",
399
- isSelected && "bg-primary/10"
400
- )}
401
- style={{ height: ROW_HEIGHT }}
402
- onClick={(e) => onSelectCommit?.(node.hash, {
403
- isMultiSelect: e.metaKey || e.ctrlKey,
404
- isRangeSelect: e.shiftKey,
405
- })}
406
- >
407
- {/* Spacing for Graph */}
408
- <div style={{ width: width, flexShrink: 0 }} />
409
-
410
- {/* Content */}
411
- <div className="flex flex-1 gap-4 overflow-hidden pr-4 items-center">
412
- <div className="flex-1 truncate flex items-center gap-2">
413
- {/* Refs Pills */}
414
- {processedTags.map((tag, idx) => {
415
- const isCurrent = currentBranch && (
416
- tag.primaryRef === currentBranch ||
417
- tag.isHead && tag.primaryRef === currentBranch
418
- );
419
- const isGitTag = tag.primaryRef.startsWith('tag:');
420
- const tagColors = isGitTag
421
- ? { textColor: '#374151', backgroundColor: '#e5e7eb' }
422
- : getBranchTagColors(tag.primaryRef);
423
-
424
- const tagElement = (
425
- <span
426
- className={cn(
427
- "text-[10px] px-1.5 rounded-full whitespace-nowrap shrink-0",
428
- isCurrent && "font-bold"
429
- )}
430
- style={{
431
- color: tagColors.textColor,
432
- backgroundColor: tagColors.backgroundColor
433
- }}
434
- title={tag.displayName}
435
- >
436
- <HighlightedText text={tag.displayName} searchQuery={searchQuery} />
437
- </span>
438
- );
439
-
440
- const branchMenuItems = getBranchTagContextMenuItems?.(tag.primaryRef) || [];
441
-
442
- if (branchMenuItems.length === 0) {
443
- return <span key={idx} className="shrink-0">{tagElement}</span>;
444
- }
445
-
446
- return (
447
- <ContextMenu
448
- key={idx}
449
- items={branchMenuItems}
450
- containerClassName="inline-flex shrink-0"
451
- >
452
- {tagElement}
453
- </ContextMenu>
454
- );
455
- })}
456
- <span className={cn("truncate min-w-0 max-w-[600px]", isSelected ? "font-semibold" : "")} title={node.message}>
457
- <HighlightedText text={node.message} searchQuery={searchQuery} />
458
- </span>
459
- </div>
460
- <div className="w-32 truncate opacity-70 text-right">
461
- <HighlightedText text={node.author_name} searchQuery={searchQuery} />
462
- </div>
463
- <div className="w-20 truncate opacity-50 font-mono text-right">
464
- <HighlightedText text={node.hash.substring(0, 7)} searchQuery={searchQuery} />
465
- </div>
466
- <div className="w-32 truncate opacity-70 text-right">
467
- {new Date(node.date).toLocaleString(undefined, {
468
- month: 'short', day: 'numeric', hour: 'numeric', minute: '2-digit'
469
- })}
470
- </div>
471
- </div>
472
- </div>
473
- </ContextMenu>
474
- );
475
- })}
476
-
477
- {/* Loading More Indicator */}
478
- {isLoadingMore && (
479
- <div className="flex items-center justify-center py-8 border-b border-base-300">
480
- <span className="loading loading-spinner text-base-content/50"></span>
481
- <span className="ml-2 text-sm opacity-70">Loading more commits...</span>
482
- </div>
483
- )}
484
- </div>
485
- </div>
486
- </div>
487
- </div>
488
- );
489
- });