git-remote-ops 0.1.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.
Files changed (245) hide show
  1. package/AGENTS.md +177 -0
  2. package/LICENSE +21 -0
  3. package/README.md +247 -0
  4. package/esm/_dnt.shims.js +72 -0
  5. package/esm/cli.js +217 -0
  6. package/esm/client.js +439 -0
  7. package/esm/deps/jsr.io/@cliffy/command/1.1.0/_argument_types.js +1 -0
  8. package/esm/deps/jsr.io/@cliffy/command/1.1.0/_errors.js +133 -0
  9. package/esm/deps/jsr.io/@cliffy/command/1.1.0/_spread.js +1 -0
  10. package/esm/deps/jsr.io/@cliffy/command/1.1.0/_type_utils.js +1 -0
  11. package/esm/deps/jsr.io/@cliffy/command/1.1.0/_utils.js +141 -0
  12. package/esm/deps/jsr.io/@cliffy/command/1.1.0/command.js +1861 -0
  13. package/esm/deps/jsr.io/@cliffy/command/1.1.0/help/_help_generator.js +357 -0
  14. package/esm/deps/jsr.io/@cliffy/command/1.1.0/mod.js +13 -0
  15. package/esm/deps/jsr.io/@cliffy/command/1.1.0/type.js +27 -0
  16. package/esm/deps/jsr.io/@cliffy/command/1.1.0/types/action_list.js +16 -0
  17. package/esm/deps/jsr.io/@cliffy/command/1.1.0/types/boolean.js +13 -0
  18. package/esm/deps/jsr.io/@cliffy/command/1.1.0/types/child_command.js +14 -0
  19. package/esm/deps/jsr.io/@cliffy/command/1.1.0/types/command.js +9 -0
  20. package/esm/deps/jsr.io/@cliffy/command/1.1.0/types/enum.js +24 -0
  21. package/esm/deps/jsr.io/@cliffy/command/1.1.0/types/file.js +12 -0
  22. package/esm/deps/jsr.io/@cliffy/command/1.1.0/types/integer.js +9 -0
  23. package/esm/deps/jsr.io/@cliffy/command/1.1.0/types/number.js +9 -0
  24. package/esm/deps/jsr.io/@cliffy/command/1.1.0/types/secret.js +7 -0
  25. package/esm/deps/jsr.io/@cliffy/command/1.1.0/types/string.js +9 -0
  26. package/esm/deps/jsr.io/@cliffy/command/1.1.0/types.js +2 -0
  27. package/esm/deps/jsr.io/@cliffy/command/1.1.0/upgrade/_check_version.js +26 -0
  28. package/esm/deps/jsr.io/@cliffy/flags/1.1.0/_errors.js +129 -0
  29. package/esm/deps/jsr.io/@cliffy/flags/1.1.0/_utils.js +100 -0
  30. package/esm/deps/jsr.io/@cliffy/flags/1.1.0/_validate_flags.js +166 -0
  31. package/esm/deps/jsr.io/@cliffy/flags/1.1.0/flags.js +750 -0
  32. package/esm/deps/jsr.io/@cliffy/flags/1.1.0/mod.js +55 -0
  33. package/esm/deps/jsr.io/@cliffy/flags/1.1.0/types/boolean.js +11 -0
  34. package/esm/deps/jsr.io/@cliffy/flags/1.1.0/types/integer.js +9 -0
  35. package/esm/deps/jsr.io/@cliffy/flags/1.1.0/types/number.js +11 -0
  36. package/esm/deps/jsr.io/@cliffy/flags/1.1.0/types/string.js +4 -0
  37. package/esm/deps/jsr.io/@cliffy/flags/1.1.0/types.js +1 -0
  38. package/esm/deps/jsr.io/@cliffy/internal/1.1.0/runtime/exit.js +16 -0
  39. package/esm/deps/jsr.io/@cliffy/internal/1.1.0/runtime/get_args.js +11 -0
  40. package/esm/deps/jsr.io/@cliffy/internal/1.1.0/runtime/get_columns.js +25 -0
  41. package/esm/deps/jsr.io/@cliffy/internal/1.1.0/runtime/get_env.js +18 -0
  42. package/esm/deps/jsr.io/@cliffy/internal/1.1.0/runtime/inspect.js +11 -0
  43. package/esm/deps/jsr.io/@cliffy/table/1.1.0/_layout.js +616 -0
  44. package/esm/deps/jsr.io/@cliffy/table/1.1.0/_utils.js +79 -0
  45. package/esm/deps/jsr.io/@cliffy/table/1.1.0/border.js +18 -0
  46. package/esm/deps/jsr.io/@cliffy/table/1.1.0/cell.js +190 -0
  47. package/esm/deps/jsr.io/@cliffy/table/1.1.0/column.js +117 -0
  48. package/esm/deps/jsr.io/@cliffy/table/1.1.0/consume_words.js +64 -0
  49. package/esm/deps/jsr.io/@cliffy/table/1.1.0/mod.js +42 -0
  50. package/esm/deps/jsr.io/@cliffy/table/1.1.0/row.js +82 -0
  51. package/esm/deps/jsr.io/@cliffy/table/1.1.0/table.js +341 -0
  52. package/esm/deps/jsr.io/@cliffy/table/1.1.0/unicode_width.js +101 -0
  53. package/esm/deps/jsr.io/@std/crypto/1.1.0/_types.js +2 -0
  54. package/esm/deps/jsr.io/@std/crypto/1.1.0/_wasm/lib/deno_std_wasm_crypto.internal.js +237 -0
  55. package/esm/deps/jsr.io/@std/crypto/1.1.0/_wasm/lib/deno_std_wasm_crypto.js +2277 -0
  56. package/esm/deps/jsr.io/@std/crypto/1.1.0/_wasm/mod.js +46 -0
  57. package/esm/deps/jsr.io/@std/crypto/1.1.0/aes_gcm.js +132 -0
  58. package/esm/deps/jsr.io/@std/crypto/1.1.0/crypto.js +270 -0
  59. package/esm/deps/jsr.io/@std/crypto/1.1.0/mod.js +23 -0
  60. package/esm/deps/jsr.io/@std/crypto/1.1.0/timing_safe_equal.js +61 -0
  61. package/esm/deps/jsr.io/@std/encoding/1.0.10/_common16.js +51 -0
  62. package/esm/deps/jsr.io/@std/encoding/1.0.10/_common_detach.js +13 -0
  63. package/esm/deps/jsr.io/@std/encoding/1.0.10/_types.js +2 -0
  64. package/esm/deps/jsr.io/@std/encoding/1.0.10/hex.js +87 -0
  65. package/esm/deps/jsr.io/@std/fmt/1.0.10/colors.js +903 -0
  66. package/esm/deps/jsr.io/@std/text/1.0.18/closest_string.js +46 -0
  67. package/esm/deps/jsr.io/@std/text/1.0.18/levenshtein_distance.js +127 -0
  68. package/esm/errors.js +38 -0
  69. package/esm/index.js +10 -0
  70. package/esm/logger.js +216 -0
  71. package/esm/objects/commit.js +47 -0
  72. package/esm/objects/index.js +2 -0
  73. package/esm/objects/tree.js +149 -0
  74. package/esm/pack/delta.js +179 -0
  75. package/esm/pack/index.js +3 -0
  76. package/esm/pack/objects.js +72 -0
  77. package/esm/pack/parser.js +304 -0
  78. package/esm/package.json +3 -0
  79. package/esm/protocol/index.js +3 -0
  80. package/esm/protocol/pkt_line.js +103 -0
  81. package/esm/protocol/refs.js +100 -0
  82. package/esm/protocol/upload_pack.js +259 -0
  83. package/esm/transport.js +128 -0
  84. package/esm/types.js +8 -0
  85. package/package.json +50 -0
  86. package/types/_dnt.shims.d.ts +16 -0
  87. package/types/_dnt.shims.d.ts.map +1 -0
  88. package/types/cli.d.ts +3 -0
  89. package/types/cli.d.ts.map +1 -0
  90. package/types/client.d.ts +108 -0
  91. package/types/client.d.ts.map +1 -0
  92. package/types/deps/jsr.io/@cliffy/command/1.1.0/_argument_types.d.ts +163 -0
  93. package/types/deps/jsr.io/@cliffy/command/1.1.0/_argument_types.d.ts.map +1 -0
  94. package/types/deps/jsr.io/@cliffy/command/1.1.0/_errors.d.ts +71 -0
  95. package/types/deps/jsr.io/@cliffy/command/1.1.0/_errors.d.ts.map +1 -0
  96. package/types/deps/jsr.io/@cliffy/command/1.1.0/_spread.d.ts +16 -0
  97. package/types/deps/jsr.io/@cliffy/command/1.1.0/_spread.d.ts.map +1 -0
  98. package/types/deps/jsr.io/@cliffy/command/1.1.0/_type_utils.d.ts +15 -0
  99. package/types/deps/jsr.io/@cliffy/command/1.1.0/_type_utils.d.ts.map +1 -0
  100. package/types/deps/jsr.io/@cliffy/command/1.1.0/_utils.d.ts +38 -0
  101. package/types/deps/jsr.io/@cliffy/command/1.1.0/_utils.d.ts.map +1 -0
  102. package/types/deps/jsr.io/@cliffy/command/1.1.0/command.d.ts +1086 -0
  103. package/types/deps/jsr.io/@cliffy/command/1.1.0/command.d.ts.map +1 -0
  104. package/types/deps/jsr.io/@cliffy/command/1.1.0/help/_help_generator.d.ts +33 -0
  105. package/types/deps/jsr.io/@cliffy/command/1.1.0/help/_help_generator.d.ts.map +1 -0
  106. package/types/deps/jsr.io/@cliffy/command/1.1.0/mod.d.ts +78 -0
  107. package/types/deps/jsr.io/@cliffy/command/1.1.0/mod.d.ts.map +1 -0
  108. package/types/deps/jsr.io/@cliffy/command/1.1.0/type.d.ts +51 -0
  109. package/types/deps/jsr.io/@cliffy/command/1.1.0/type.d.ts.map +1 -0
  110. package/types/deps/jsr.io/@cliffy/command/1.1.0/types/action_list.d.ts +10 -0
  111. package/types/deps/jsr.io/@cliffy/command/1.1.0/types/action_list.d.ts.map +1 -0
  112. package/types/deps/jsr.io/@cliffy/command/1.1.0/types/boolean.d.ts +10 -0
  113. package/types/deps/jsr.io/@cliffy/command/1.1.0/types/boolean.d.ts.map +1 -0
  114. package/types/deps/jsr.io/@cliffy/command/1.1.0/types/child_command.d.ts +10 -0
  115. package/types/deps/jsr.io/@cliffy/command/1.1.0/types/child_command.d.ts.map +1 -0
  116. package/types/deps/jsr.io/@cliffy/command/1.1.0/types/command.d.ts +8 -0
  117. package/types/deps/jsr.io/@cliffy/command/1.1.0/types/command.d.ts.map +1 -0
  118. package/types/deps/jsr.io/@cliffy/command/1.1.0/types/enum.d.ts +11 -0
  119. package/types/deps/jsr.io/@cliffy/command/1.1.0/types/enum.d.ts.map +1 -0
  120. package/types/deps/jsr.io/@cliffy/command/1.1.0/types/file.d.ts +6 -0
  121. package/types/deps/jsr.io/@cliffy/command/1.1.0/types/file.d.ts.map +1 -0
  122. package/types/deps/jsr.io/@cliffy/command/1.1.0/types/integer.d.ts +8 -0
  123. package/types/deps/jsr.io/@cliffy/command/1.1.0/types/integer.d.ts.map +1 -0
  124. package/types/deps/jsr.io/@cliffy/command/1.1.0/types/number.d.ts +8 -0
  125. package/types/deps/jsr.io/@cliffy/command/1.1.0/types/number.d.ts.map +1 -0
  126. package/types/deps/jsr.io/@cliffy/command/1.1.0/types/secret.d.ts +6 -0
  127. package/types/deps/jsr.io/@cliffy/command/1.1.0/types/secret.d.ts.map +1 -0
  128. package/types/deps/jsr.io/@cliffy/command/1.1.0/types/string.d.ts +8 -0
  129. package/types/deps/jsr.io/@cliffy/command/1.1.0/types/string.d.ts.map +1 -0
  130. package/types/deps/jsr.io/@cliffy/command/1.1.0/types.d.ts +161 -0
  131. package/types/deps/jsr.io/@cliffy/command/1.1.0/types.d.ts.map +1 -0
  132. package/types/deps/jsr.io/@cliffy/command/1.1.0/upgrade/_check_version.d.ts +4 -0
  133. package/types/deps/jsr.io/@cliffy/command/1.1.0/upgrade/_check_version.d.ts.map +1 -0
  134. package/types/deps/jsr.io/@cliffy/flags/1.1.0/_errors.d.ts +67 -0
  135. package/types/deps/jsr.io/@cliffy/flags/1.1.0/_errors.d.ts.map +1 -0
  136. package/types/deps/jsr.io/@cliffy/flags/1.1.0/_utils.d.ts +17 -0
  137. package/types/deps/jsr.io/@cliffy/flags/1.1.0/_utils.d.ts.map +1 -0
  138. package/types/deps/jsr.io/@cliffy/flags/1.1.0/_validate_flags.d.ts +11 -0
  139. package/types/deps/jsr.io/@cliffy/flags/1.1.0/_validate_flags.d.ts.map +1 -0
  140. package/types/deps/jsr.io/@cliffy/flags/1.1.0/flags.d.ts +154 -0
  141. package/types/deps/jsr.io/@cliffy/flags/1.1.0/flags.d.ts.map +1 -0
  142. package/types/deps/jsr.io/@cliffy/flags/1.1.0/mod.d.ts +57 -0
  143. package/types/deps/jsr.io/@cliffy/flags/1.1.0/mod.d.ts.map +1 -0
  144. package/types/deps/jsr.io/@cliffy/flags/1.1.0/types/boolean.d.ts +4 -0
  145. package/types/deps/jsr.io/@cliffy/flags/1.1.0/types/boolean.d.ts.map +1 -0
  146. package/types/deps/jsr.io/@cliffy/flags/1.1.0/types/integer.d.ts +4 -0
  147. package/types/deps/jsr.io/@cliffy/flags/1.1.0/types/integer.d.ts.map +1 -0
  148. package/types/deps/jsr.io/@cliffy/flags/1.1.0/types/number.d.ts +4 -0
  149. package/types/deps/jsr.io/@cliffy/flags/1.1.0/types/number.d.ts.map +1 -0
  150. package/types/deps/jsr.io/@cliffy/flags/1.1.0/types/string.d.ts +4 -0
  151. package/types/deps/jsr.io/@cliffy/flags/1.1.0/types/string.d.ts.map +1 -0
  152. package/types/deps/jsr.io/@cliffy/flags/1.1.0/types.d.ts +170 -0
  153. package/types/deps/jsr.io/@cliffy/flags/1.1.0/types.d.ts.map +1 -0
  154. package/types/deps/jsr.io/@cliffy/internal/1.1.0/runtime/exit.d.ts +8 -0
  155. package/types/deps/jsr.io/@cliffy/internal/1.1.0/runtime/exit.d.ts.map +1 -0
  156. package/types/deps/jsr.io/@cliffy/internal/1.1.0/runtime/get_args.d.ts +7 -0
  157. package/types/deps/jsr.io/@cliffy/internal/1.1.0/runtime/get_args.d.ts.map +1 -0
  158. package/types/deps/jsr.io/@cliffy/internal/1.1.0/runtime/get_columns.d.ts +7 -0
  159. package/types/deps/jsr.io/@cliffy/internal/1.1.0/runtime/get_columns.d.ts.map +1 -0
  160. package/types/deps/jsr.io/@cliffy/internal/1.1.0/runtime/get_env.d.ts +8 -0
  161. package/types/deps/jsr.io/@cliffy/internal/1.1.0/runtime/get_env.d.ts.map +1 -0
  162. package/types/deps/jsr.io/@cliffy/internal/1.1.0/runtime/inspect.d.ts +7 -0
  163. package/types/deps/jsr.io/@cliffy/internal/1.1.0/runtime/inspect.d.ts.map +1 -0
  164. package/types/deps/jsr.io/@cliffy/table/1.1.0/_layout.d.ts +108 -0
  165. package/types/deps/jsr.io/@cliffy/table/1.1.0/_layout.d.ts.map +1 -0
  166. package/types/deps/jsr.io/@cliffy/table/1.1.0/_utils.d.ts +26 -0
  167. package/types/deps/jsr.io/@cliffy/table/1.1.0/_utils.d.ts.map +1 -0
  168. package/types/deps/jsr.io/@cliffy/table/1.1.0/border.d.ts +21 -0
  169. package/types/deps/jsr.io/@cliffy/table/1.1.0/border.d.ts.map +1 -0
  170. package/types/deps/jsr.io/@cliffy/table/1.1.0/cell.d.ts +155 -0
  171. package/types/deps/jsr.io/@cliffy/table/1.1.0/cell.d.ts.map +1 -0
  172. package/types/deps/jsr.io/@cliffy/table/1.1.0/column.d.ts +97 -0
  173. package/types/deps/jsr.io/@cliffy/table/1.1.0/column.d.ts.map +1 -0
  174. package/types/deps/jsr.io/@cliffy/table/1.1.0/consume_words.d.ts +30 -0
  175. package/types/deps/jsr.io/@cliffy/table/1.1.0/consume_words.d.ts.map +1 -0
  176. package/types/deps/jsr.io/@cliffy/table/1.1.0/mod.d.ts +43 -0
  177. package/types/deps/jsr.io/@cliffy/table/1.1.0/mod.d.ts.map +1 -0
  178. package/types/deps/jsr.io/@cliffy/table/1.1.0/row.d.ts +67 -0
  179. package/types/deps/jsr.io/@cliffy/table/1.1.0/row.d.ts.map +1 -0
  180. package/types/deps/jsr.io/@cliffy/table/1.1.0/table.d.ts +235 -0
  181. package/types/deps/jsr.io/@cliffy/table/1.1.0/table.d.ts.map +1 -0
  182. package/types/deps/jsr.io/@cliffy/table/1.1.0/unicode_width.d.ts +40 -0
  183. package/types/deps/jsr.io/@cliffy/table/1.1.0/unicode_width.d.ts.map +1 -0
  184. package/types/deps/jsr.io/@std/crypto/1.1.0/_types.d.ts +9 -0
  185. package/types/deps/jsr.io/@std/crypto/1.1.0/_types.d.ts.map +1 -0
  186. package/types/deps/jsr.io/@std/crypto/1.1.0/_wasm/lib/deno_std_wasm_crypto.d.ts +2 -0
  187. package/types/deps/jsr.io/@std/crypto/1.1.0/_wasm/lib/deno_std_wasm_crypto.d.ts.map +1 -0
  188. package/types/deps/jsr.io/@std/crypto/1.1.0/_wasm/lib/deno_std_wasm_crypto.internal.d.ts +69 -0
  189. package/types/deps/jsr.io/@std/crypto/1.1.0/_wasm/lib/deno_std_wasm_crypto.internal.d.ts.map +1 -0
  190. package/types/deps/jsr.io/@std/crypto/1.1.0/_wasm/mod.d.ts +13 -0
  191. package/types/deps/jsr.io/@std/crypto/1.1.0/_wasm/mod.d.ts.map +1 -0
  192. package/types/deps/jsr.io/@std/crypto/1.1.0/aes_gcm.d.ts +76 -0
  193. package/types/deps/jsr.io/@std/crypto/1.1.0/aes_gcm.d.ts.map +1 -0
  194. package/types/deps/jsr.io/@std/crypto/1.1.0/crypto.d.ts +149 -0
  195. package/types/deps/jsr.io/@std/crypto/1.1.0/crypto.d.ts.map +1 -0
  196. package/types/deps/jsr.io/@std/crypto/1.1.0/mod.d.ts +22 -0
  197. package/types/deps/jsr.io/@std/crypto/1.1.0/mod.d.ts.map +1 -0
  198. package/types/deps/jsr.io/@std/crypto/1.1.0/timing_safe_equal.d.ts +40 -0
  199. package/types/deps/jsr.io/@std/crypto/1.1.0/timing_safe_equal.d.ts.map +1 -0
  200. package/types/deps/jsr.io/@std/encoding/1.0.10/_common16.d.ts +23 -0
  201. package/types/deps/jsr.io/@std/encoding/1.0.10/_common16.d.ts.map +1 -0
  202. package/types/deps/jsr.io/@std/encoding/1.0.10/_common_detach.d.ts +4 -0
  203. package/types/deps/jsr.io/@std/encoding/1.0.10/_common_detach.d.ts.map +1 -0
  204. package/types/deps/jsr.io/@std/encoding/1.0.10/_types.d.ts +9 -0
  205. package/types/deps/jsr.io/@std/encoding/1.0.10/_types.d.ts.map +1 -0
  206. package/types/deps/jsr.io/@std/encoding/1.0.10/hex.d.ts +39 -0
  207. package/types/deps/jsr.io/@std/encoding/1.0.10/hex.d.ts.map +1 -0
  208. package/types/deps/jsr.io/@std/fmt/1.0.10/colors.d.ts +700 -0
  209. package/types/deps/jsr.io/@std/fmt/1.0.10/colors.d.ts.map +1 -0
  210. package/types/deps/jsr.io/@std/text/1.0.18/closest_string.d.ts +42 -0
  211. package/types/deps/jsr.io/@std/text/1.0.18/closest_string.d.ts.map +1 -0
  212. package/types/deps/jsr.io/@std/text/1.0.18/levenshtein_distance.d.ts +23 -0
  213. package/types/deps/jsr.io/@std/text/1.0.18/levenshtein_distance.d.ts.map +1 -0
  214. package/types/errors.d.ts +73 -0
  215. package/types/errors.d.ts.map +1 -0
  216. package/types/index.d.ts +14 -0
  217. package/types/index.d.ts.map +1 -0
  218. package/types/logger.d.ts +70 -0
  219. package/types/logger.d.ts.map +1 -0
  220. package/types/objects/commit.d.ts +23 -0
  221. package/types/objects/commit.d.ts.map +1 -0
  222. package/types/objects/index.d.ts +3 -0
  223. package/types/objects/index.d.ts.map +1 -0
  224. package/types/objects/tree.d.ts +49 -0
  225. package/types/objects/tree.d.ts.map +1 -0
  226. package/types/pack/delta.d.ts +47 -0
  227. package/types/pack/delta.d.ts.map +1 -0
  228. package/types/pack/index.d.ts +4 -0
  229. package/types/pack/index.d.ts.map +1 -0
  230. package/types/pack/objects.d.ts +53 -0
  231. package/types/pack/objects.d.ts.map +1 -0
  232. package/types/pack/parser.d.ts +61 -0
  233. package/types/pack/parser.d.ts.map +1 -0
  234. package/types/protocol/index.d.ts +4 -0
  235. package/types/protocol/index.d.ts.map +1 -0
  236. package/types/protocol/pkt_line.d.ts +44 -0
  237. package/types/protocol/pkt_line.d.ts.map +1 -0
  238. package/types/protocol/refs.d.ts +40 -0
  239. package/types/protocol/refs.d.ts.map +1 -0
  240. package/types/protocol/upload_pack.d.ts +45 -0
  241. package/types/protocol/upload_pack.d.ts.map +1 -0
  242. package/types/transport.d.ts +24 -0
  243. package/types/transport.d.ts.map +1 -0
  244. package/types/types.d.ts +121 -0
  245. package/types/types.d.ts.map +1 -0
@@ -0,0 +1,46 @@
1
+ // Copyright 2018-2026 the Deno authors. MIT license.
2
+ // This module is browser compatible.
3
+ import { levenshteinDistance } from "./levenshtein_distance.js";
4
+ /**
5
+ * Finds the most similar string from an array of strings.
6
+ *
7
+ * By default, calculates the distance between words using the
8
+ * {@link https://en.wikipedia.org/wiki/Levenshtein_distance | Levenshtein distance}.
9
+ *
10
+ * @example Usage
11
+ * ```ts
12
+ * import { closestString } from "@std/text/closest-string";
13
+ * import { assertEquals } from "@std/assert";
14
+ *
15
+ * const possibleWords = ["length", "size", "blah", "help"];
16
+ * const suggestion = closestString("hep", possibleWords);
17
+ *
18
+ * assertEquals(suggestion, "help");
19
+ * ```
20
+ *
21
+ * @param givenWord The string to measure distance against
22
+ * @param possibleWords The string-array to pick the closest string from
23
+ * @param options The options for the comparison.
24
+ * @returns The closest string
25
+ */
26
+ export function closestString(givenWord, possibleWords, options) {
27
+ if (possibleWords.length === 0) {
28
+ throw new TypeError("When using closestString(), the possibleWords array must contain at least one word");
29
+ }
30
+ const { caseSensitive, compareFn = levenshteinDistance } = { ...options };
31
+ if (!caseSensitive) {
32
+ givenWord = givenWord.toLowerCase();
33
+ }
34
+ let nearestWord = possibleWords[0];
35
+ let closestStringDistance = Infinity;
36
+ for (const each of possibleWords) {
37
+ const distance = caseSensitive
38
+ ? compareFn(givenWord, each)
39
+ : compareFn(givenWord, each.toLowerCase());
40
+ if (distance < closestStringDistance) {
41
+ nearestWord = each;
42
+ closestStringDistance = distance;
43
+ }
44
+ }
45
+ return nearestWord;
46
+ }
@@ -0,0 +1,127 @@
1
+ // Copyright 2018-2026 the Deno authors. MIT license.
2
+ // This module is browser compatible.
3
+ const { ceil } = Math;
4
+ // This implements Myers' bit-vector algorithm as described here:
5
+ // https://dl.acm.org/doi/pdf/10.1145/316542.316550
6
+ const peq = new Uint32Array(0x110000);
7
+ function myers32(t, p) {
8
+ const n = t.length;
9
+ const m = p.length;
10
+ for (let i = 0; i < m; i++) {
11
+ peq[p[i].codePointAt(0)] |= 1 << i;
12
+ }
13
+ const last = m - 1;
14
+ let pv = -1;
15
+ let mv = 0;
16
+ let score = m;
17
+ for (let j = 0; j < n; j++) {
18
+ const eq = peq[t[j].codePointAt(0)];
19
+ const xv = eq | mv;
20
+ const xh = (((eq & pv) + pv) ^ pv) | eq;
21
+ let ph = mv | ~(xh | pv);
22
+ let mh = pv & xh;
23
+ score += ((ph >>> last) & 1) - ((mh >>> last) & 1);
24
+ // Set the horizontal delta in the first row to +1
25
+ // because we are computing the distance between two full strings.
26
+ ph = (ph << 1) | 1;
27
+ mh = mh << 1;
28
+ pv = mh | ~(xv | ph);
29
+ mv = ph & xv;
30
+ }
31
+ for (let i = 0; i < m; i++) {
32
+ peq[p[i].codePointAt(0)] = 0;
33
+ }
34
+ return score;
35
+ }
36
+ function myersX(t, p) {
37
+ const n = t.length;
38
+ const m = p.length;
39
+ // Initialize the horizontal deltas to +1.
40
+ const h = new Int8Array(n).fill(1);
41
+ const bmax = ceil(m / 32) - 1;
42
+ // Process the blocks row by row so that we can use the fixed-size peq array.
43
+ for (let b = 0; b < bmax; b++) {
44
+ const start = b * 32;
45
+ const end = (b + 1) * 32;
46
+ for (let i = start; i < end; i++) {
47
+ peq[p[i].codePointAt(0)] |= 1 << i;
48
+ }
49
+ let pv = -1;
50
+ let mv = 0;
51
+ for (let j = 0; j < n; j++) {
52
+ const hin = h[j];
53
+ let eq = peq[t[j].codePointAt(0)];
54
+ const xv = eq | mv;
55
+ eq |= hin >>> 31;
56
+ const xh = (((eq & pv) + pv) ^ pv) | eq;
57
+ let ph = mv | ~(xh | pv);
58
+ let mh = pv & xh;
59
+ h[j] = (ph >>> 31) - (mh >>> 31);
60
+ ph = (ph << 1) | (-hin >>> 31);
61
+ mh = (mh << 1) | (hin >>> 31);
62
+ pv = mh | ~(xv | ph);
63
+ mv = ph & xv;
64
+ }
65
+ for (let i = start; i < end; i++) {
66
+ peq[p[i].codePointAt(0)] = 0;
67
+ }
68
+ }
69
+ const start = bmax * 32;
70
+ for (let i = start; i < m; i++) {
71
+ peq[p[i].codePointAt(0)] |= 1 << i;
72
+ }
73
+ const last = m - 1;
74
+ let pv = -1;
75
+ let mv = 0;
76
+ let score = m;
77
+ for (let j = 0; j < n; j++) {
78
+ const hin = h[j];
79
+ let eq = peq[t[j].codePointAt(0)];
80
+ const xv = eq | mv;
81
+ eq |= hin >>> 31;
82
+ const xh = (((eq & pv) + pv) ^ pv) | eq;
83
+ let ph = mv | ~(xh | pv);
84
+ let mh = pv & xh;
85
+ score += ((ph >>> last) & 1) - ((mh >>> last) & 1);
86
+ ph = (ph << 1) | (-hin >>> 31);
87
+ mh = (mh << 1) | (hin >>> 31);
88
+ pv = mh | ~(xv | ph);
89
+ mv = ph & xv;
90
+ }
91
+ for (let i = start; i < m; i++) {
92
+ peq[p[i].codePointAt(0)] = 0;
93
+ }
94
+ return score;
95
+ }
96
+ /**
97
+ * Calculates the
98
+ * {@link https://en.wikipedia.org/wiki/Levenshtein_distance | Levenshtein distance}
99
+ * between two strings.
100
+ *
101
+ * > [!NOTE]
102
+ * > The complexity of this function is O(m * n), where m and n are the lengths
103
+ * > of the two strings. It's recommended to limit the length and validate input
104
+ * > if arbitrarily accepting input.
105
+ *
106
+ * @example Usage
107
+ * ```ts
108
+ * import { levenshteinDistance } from "@std/text/levenshtein-distance";
109
+ * import { assertEquals } from "@std/assert";
110
+ *
111
+ * assertEquals(levenshteinDistance("aa", "bb"), 2);
112
+ * ```
113
+ * @param str1 The first string.
114
+ * @param str2 The second string.
115
+ * @returns The Levenshtein distance between the two strings.
116
+ */
117
+ export function levenshteinDistance(str1, str2) {
118
+ let t = [...str1];
119
+ let p = [...str2];
120
+ if (t.length < p.length) {
121
+ [p, t] = [t, p];
122
+ }
123
+ if (p.length === 0) {
124
+ return t.length;
125
+ }
126
+ return p.length <= 32 ? myers32(t, p) : myersX(t, p);
127
+ }
package/esm/errors.js ADDED
@@ -0,0 +1,38 @@
1
+ /**
2
+ * @module errors
3
+ *
4
+ * Tagged error classes returned (never thrown) from every public function.
5
+ *
6
+ * Each class wraps `better-result`'s `TaggedError` so failures carry a
7
+ * narrowable `_tag` field plus structured context (offsets, sha, http
8
+ * status, etc.). The {@link GitRemoteOpsError} union sweeps them up for
9
+ * `Result<T, GitRemoteOpsError>` return types throughout the public API.
10
+ *
11
+ * A `cause` field, when present, is the underlying thrown value preserved
12
+ * for `Error.cause`-style chaining.
13
+ */
14
+ import { TaggedError } from "better-result";
15
+ /** Anything that goes wrong while parsing a packfile or applying a delta. */
16
+ export class PackParseError extends TaggedError("PackParseError")() {
17
+ }
18
+ /** Raised when a commit/tree body fails structural validation. */
19
+ export class ObjectDecodeError extends TaggedError("ObjectDecodeError")() {
20
+ }
21
+ /** Malformed pkt-line framing (bad length prefix or truncation). */
22
+ export class PktLineError extends TaggedError("PktLineError")() {
23
+ }
24
+ /** Server returned a `git-upload-pack` response we couldn't interpret. */
25
+ export class UploadPackError extends TaggedError("UploadPackError")() {
26
+ }
27
+ /** Anything HTTP — network failure, non-2xx status, body-read error. */
28
+ export class TransportError extends TaggedError("TransportError")() {
29
+ }
30
+ /** The requested ref didn't appear in the server's advertisement. */
31
+ export class RefNotFoundError extends TaggedError("RefNotFoundError")() {
32
+ }
33
+ /** An object referenced by sha wasn't present in the materialized store. */
34
+ export class ObjectNotFoundError extends TaggedError("ObjectNotFoundError")() {
35
+ }
36
+ /** Tree-walk couldn't reach the requested path. */
37
+ export class PathNotFoundError extends TaggedError("PathNotFoundError")() {
38
+ }
package/esm/index.js ADDED
@@ -0,0 +1,10 @@
1
+ /**
2
+ * @module git-remote-ops
3
+ *
4
+ * Public entry point. Re-exports the {@link RemoteGit} client, the
5
+ * {@link Logger}, every {@link GitRemoteOpsError} subclass, and the shared
6
+ * type vocabulary. The CLI ships separately under `./cli`.
7
+ */
8
+ export { RemoteGit } from "./client.js";
9
+ export { Logger, NULL_LOGGER } from "./logger.js";
10
+ export { ObjectDecodeError, ObjectNotFoundError, PackParseError, PathNotFoundError, PktLineError, RefNotFoundError, TransportError, UploadPackError, } from "./errors.js";
package/esm/logger.js ADDED
@@ -0,0 +1,216 @@
1
+ /**
2
+ * @module logger
3
+ *
4
+ * A tiny levelled logger with namespaced children, optional ANSI colour, and
5
+ * cumulative metrics for HTTP and pack-parsing activity.
6
+ *
7
+ * Output goes to a configurable sink (defaults to `console.error`). Levels
8
+ * stack: `info ⊂ debug ⊂ trace`. The library never logs above `info` unless
9
+ * `--verbose` / `--debug` is set on the CLI or the caller passes its own
10
+ * configured logger.
11
+ */
12
+ import * as dntShim from "./_dnt.shims.js";
13
+ /** Numeric ranking — higher means "more verbose". Used for level comparison. */
14
+ const LEVEL_RANK = {
15
+ silent: 0,
16
+ info: 1,
17
+ debug: 2,
18
+ trace: 3,
19
+ };
20
+ /** ANSI escape codes. Disabled when stderr isn't a TTY or `NO_COLOR` is set. */
21
+ const ANSI = {
22
+ reset: "\x1b[0m",
23
+ dim: "\x1b[2m",
24
+ bold: "\x1b[1m",
25
+ red: "\x1b[31m",
26
+ green: "\x1b[32m",
27
+ yellow: "\x1b[33m",
28
+ blue: "\x1b[34m",
29
+ magenta: "\x1b[35m",
30
+ cyan: "\x1b[36m",
31
+ gray: "\x1b[90m",
32
+ };
33
+ /** Per-level colour token. `silent` has none — it never emits. */
34
+ const LEVEL_COLOR = {
35
+ info: ANSI.cyan,
36
+ debug: ANSI.blue,
37
+ trace: ANSI.gray,
38
+ };
39
+ function emptyMetrics() {
40
+ return {
41
+ httpRequests: 0,
42
+ httpBytesIn: 0,
43
+ httpBytesOut: 0,
44
+ httpDurationMs: 0,
45
+ packObjects: 0,
46
+ packBytes: 0,
47
+ packParseMs: 0,
48
+ byType: { commit: 0, tree: 0, blob: 0, tag: 0 },
49
+ };
50
+ }
51
+ function detectColor() {
52
+ try {
53
+ if (dntShim.Deno.env.get("NO_COLOR"))
54
+ return false;
55
+ return dntShim.Deno.stderr.isTerminal();
56
+ }
57
+ catch {
58
+ return false;
59
+ }
60
+ }
61
+ /** Pretty-print a byte count with binary-prefix units. */
62
+ export function formatBytes(n) {
63
+ if (n < 1024)
64
+ return `${n}B`;
65
+ if (n < 1024 * 1024)
66
+ return `${(n / 1024).toFixed(1)}KB`;
67
+ if (n < 1024 * 1024 * 1024)
68
+ return `${(n / 1024 / 1024).toFixed(2)}MB`;
69
+ return `${(n / 1024 / 1024 / 1024).toFixed(2)}GB`;
70
+ }
71
+ /** Pretty-print a millisecond duration, scaling unit to magnitude. */
72
+ export function formatDuration(ms) {
73
+ if (ms < 1)
74
+ return `${ms.toFixed(2)}ms`;
75
+ if (ms < 1000)
76
+ return `${ms.toFixed(1)}ms`;
77
+ if (ms < 60_000)
78
+ return `${(ms / 1000).toFixed(2)}s`;
79
+ const min = Math.floor(ms / 60_000);
80
+ const s = ((ms % 60_000) / 1000).toFixed(1);
81
+ return `${min}m${s}s`;
82
+ }
83
+ /**
84
+ * Levelled logger with namespaced children and shared metrics.
85
+ *
86
+ * Children share their parent's `metrics` object so that any `recordHttp` /
87
+ * `recordPack` call anywhere in the tree contributes to the same totals
88
+ * surfaced by {@link Logger.summary}.
89
+ */
90
+ export class Logger {
91
+ level;
92
+ metrics;
93
+ sink;
94
+ namespace;
95
+ collect;
96
+ color;
97
+ timestamps;
98
+ epoch;
99
+ constructor(options = {}, namespace = "") {
100
+ this.level = options.level ?? "silent";
101
+ this.sink = options.sink ?? ((line) => console.error(line));
102
+ this.collect = options.collectMetrics ?? true;
103
+ this.namespace = namespace;
104
+ this.color = options.color ?? detectColor();
105
+ this.timestamps = options.timestamps ?? true;
106
+ this.metrics = emptyMetrics();
107
+ this.epoch = performance.now();
108
+ }
109
+ /** Spawn a logger that prefixes its messages with `parent.namespace + "." + namespace`. */
110
+ child(namespace) {
111
+ const ns = this.namespace ? `${this.namespace}.${namespace}` : namespace;
112
+ const c = new Logger({
113
+ level: this.level,
114
+ sink: this.sink,
115
+ collectMetrics: this.collect,
116
+ color: this.color,
117
+ timestamps: this.timestamps,
118
+ }, ns);
119
+ c.metrics = this.metrics;
120
+ c.epoch = this.epoch;
121
+ return c;
122
+ }
123
+ enabled(level) {
124
+ return LEVEL_RANK[this.level] >= LEVEL_RANK[level];
125
+ }
126
+ paint(s, color) {
127
+ return this.color ? `${color}${s}${ANSI.reset}` : s;
128
+ }
129
+ emit(level, message) {
130
+ if (!this.enabled(level))
131
+ return;
132
+ const parts = [];
133
+ if (this.timestamps) {
134
+ const elapsed = performance.now() - this.epoch;
135
+ parts.push(this.paint(`+${formatDuration(elapsed).padStart(7)}`, ANSI.gray));
136
+ }
137
+ parts.push(this.paint(level.padEnd(5), LEVEL_COLOR[level]));
138
+ if (this.namespace)
139
+ parts.push(this.paint(this.namespace.padEnd(16), ANSI.dim));
140
+ parts.push(message);
141
+ this.sink(parts.join(" "));
142
+ }
143
+ /** Emit at `info`. No-op if level is `silent`. */
144
+ info(message) {
145
+ this.emit("info", message);
146
+ }
147
+ /** Emit at `debug`. No-op unless level ≥ `debug`. */
148
+ debug(message) {
149
+ this.emit("debug", message);
150
+ }
151
+ /** Emit at `trace`. No-op unless level is `trace`. */
152
+ trace(message) {
153
+ this.emit("trace", message);
154
+ }
155
+ /** Time an async op; emits at `level` with duration. */
156
+ async time(level, label, fn) {
157
+ if (!this.enabled(level))
158
+ return await fn();
159
+ const start = performance.now();
160
+ try {
161
+ return await fn();
162
+ }
163
+ finally {
164
+ this.emit(level, `${label} ${this.paint(formatDuration(performance.now() - start), ANSI.yellow)}`);
165
+ }
166
+ }
167
+ /** Add one HTTP request's totals to {@link Logger.metrics}. */
168
+ recordHttp(opts) {
169
+ if (!this.collect)
170
+ return;
171
+ this.metrics.httpRequests++;
172
+ this.metrics.httpBytesIn += opts.bytesIn;
173
+ this.metrics.httpBytesOut += opts.bytesOut;
174
+ this.metrics.httpDurationMs += opts.durationMs;
175
+ }
176
+ /** Add one parsed pack's totals to {@link Logger.metrics}. */
177
+ recordPack(opts) {
178
+ if (!this.collect)
179
+ return;
180
+ this.metrics.packBytes += opts.bytes;
181
+ this.metrics.packParseMs += opts.durationMs;
182
+ for (const k of ["commit", "tree", "blob", "tag"]) {
183
+ this.metrics.byType[k] += opts.byType[k];
184
+ this.metrics.packObjects += opts.byType[k];
185
+ }
186
+ }
187
+ /** Render an aligned multi-line table of {@link Logger.metrics}. CLI uses this for `--stats`. */
188
+ summary() {
189
+ const m = this.metrics;
190
+ const bold = (s) => this.paint(s, ANSI.bold);
191
+ const dim = (s) => this.paint(s, ANSI.dim);
192
+ const num = (s) => this.paint(s, ANSI.green);
193
+ const rows = [
194
+ ["HTTP requests", num(String(m.httpRequests))],
195
+ [" bytes in", num(formatBytes(m.httpBytesIn))],
196
+ [" bytes out", num(formatBytes(m.httpBytesOut))],
197
+ [" total time", num(formatDuration(m.httpDurationMs))],
198
+ [
199
+ " avg / req",
200
+ num(m.httpRequests > 0 ? formatDuration(m.httpDurationMs / m.httpRequests) : "n/a"),
201
+ ],
202
+ ["Pack objects", num(String(m.packObjects))],
203
+ [
204
+ " by type",
205
+ dim(`${m.byType.commit} commit, ${m.byType.tree} tree, ${m.byType.blob} blob, ${m.byType.tag} tag`),
206
+ ],
207
+ [" bytes", num(formatBytes(m.packBytes))],
208
+ [" parse time", num(formatDuration(m.packParseMs))],
209
+ ];
210
+ const labelW = Math.max(...rows.map(([l]) => l.length));
211
+ const lines = rows.map(([l, v]) => ` ${l.padEnd(labelW)} ${v}`);
212
+ return [bold("── stats ──────────────────────────────"), ...lines].join("\n");
213
+ }
214
+ }
215
+ /** Shared no-op logger. Use as a default when callers don't pass one in. */
216
+ export const NULL_LOGGER = new Logger({ level: "silent", collectMetrics: false });
@@ -0,0 +1,47 @@
1
+ /**
2
+ * @module objects-commit
3
+ *
4
+ * Decoder for the loose-commit object body.
5
+ *
6
+ * A commit is a header block followed by a blank line and a freeform message.
7
+ * Each header line is `<field> <value>` and only the first occurrence of any
8
+ * header is significant for our purposes (Git allows multiple `parent` lines
9
+ * for merge commits; this client deliberately keeps just the first since it
10
+ * only ever walks toward the root snapshot).
11
+ */
12
+ import { Result } from "better-result";
13
+ import { ObjectDecodeError } from "../errors.js";
14
+ const decoder = new TextDecoder();
15
+ /**
16
+ * Parse the body of a commit object.
17
+ *
18
+ * @param content Uncompressed commit bytes (no `commit <size>\0` header).
19
+ * @returns The decoded {@link CommitInfo}, or {@link ObjectDecodeError} if the
20
+ * mandatory `tree` field is absent.
21
+ */
22
+ export function parseCommit(content) {
23
+ const text = decoder.decode(content);
24
+ const headerEnd = text.indexOf("\n\n");
25
+ const header = headerEnd >= 0 ? text.slice(0, headerEnd) : text;
26
+ const fields = new Map();
27
+ for (const line of header.split("\n")) {
28
+ const space = line.indexOf(" ");
29
+ if (space <= 0 || fields.has(line.slice(0, space)))
30
+ continue;
31
+ fields.set(line.slice(0, space), line.slice(space + 1));
32
+ }
33
+ const tree = fields.get("tree");
34
+ if (!tree) {
35
+ return Result.err(new ObjectDecodeError({
36
+ reason: "missing-tree",
37
+ message: "commit missing tree field",
38
+ objectType: "commit",
39
+ }));
40
+ }
41
+ return Result.ok({
42
+ tree,
43
+ parent: fields.get("parent"),
44
+ author: fields.get("author"),
45
+ committer: fields.get("committer"),
46
+ });
47
+ }
@@ -0,0 +1,2 @@
1
+ export * from "./commit.js";
2
+ export * from "./tree.js";
@@ -0,0 +1,149 @@
1
+ /**
2
+ * @module objects-tree
3
+ *
4
+ * Tree decoder and helpers for walking trees / resolving paths to blobs.
5
+ *
6
+ * A tree entry on disk is `<octal-mode> <name>\0<20-byte-sha>` with no length
7
+ * prefix; entries are concatenated. Subtrees use mode `"40000"`; everything
8
+ * else (regular files, executable files, symlinks, gitlinks) is treated as a
9
+ * leaf by {@link walkTree}.
10
+ */
11
+ import { Result } from "better-result";
12
+ import { encodeHex } from "../deps/jsr.io/@std/encoding/1.0.10/hex.js";
13
+ import { ObjectDecodeError, PathNotFoundError } from "../errors.js";
14
+ /** Octal mode marker for a subtree entry. */
15
+ const TREE_MODE = "40000";
16
+ /** ASCII space — separates mode from name. */
17
+ const SPACE = 0x20;
18
+ /** ASCII NUL — terminates the name field. */
19
+ const NUL = 0x00;
20
+ /** Raw SHA-1 width in bytes. */
21
+ const SHA_BYTES = 20;
22
+ const decoder = new TextDecoder();
23
+ /**
24
+ * Parse a tree body into its entries, in the order they appear on disk.
25
+ *
26
+ * @param content Uncompressed tree bytes (no `tree <size>\0` header).
27
+ * @returns Ordered entries, or {@link ObjectDecodeError} on a malformed entry.
28
+ */
29
+ export function parseTree(content) {
30
+ const entries = [];
31
+ let offset = 0;
32
+ while (offset < content.length) {
33
+ const space = content.indexOf(SPACE, offset);
34
+ if (space < 0) {
35
+ return Result.err(new ObjectDecodeError({
36
+ reason: "missing-mode-separator",
37
+ message: "tree entry missing mode separator",
38
+ objectType: "tree",
39
+ }));
40
+ }
41
+ const nul = content.indexOf(NUL, space + 1);
42
+ if (nul < 0 || nul + 1 + SHA_BYTES > content.length) {
43
+ return Result.err(new ObjectDecodeError({
44
+ reason: "truncated-tree-entry",
45
+ message: "tree entry missing name terminator or sha",
46
+ objectType: "tree",
47
+ }));
48
+ }
49
+ entries.push({
50
+ mode: decoder.decode(content.subarray(offset, space)),
51
+ name: decoder.decode(content.subarray(space + 1, nul)),
52
+ sha: encodeHex(content.subarray(nul + 1, nul + 1 + SHA_BYTES)),
53
+ });
54
+ offset = nul + 1 + SHA_BYTES;
55
+ }
56
+ return Result.ok(entries);
57
+ }
58
+ /**
59
+ * Recursively flatten a tree into its leaf entries.
60
+ *
61
+ * Subtrees are followed; every other mode is treated as a file and emitted
62
+ * with its full path joined from `pathPrefix`. Requires that every reachable
63
+ * tree object is already present in `objects` — typically the case after a
64
+ * full (unfiltered) snapshot fetch.
65
+ *
66
+ * @param pathPrefix Internal: prefix prepended to entry names while recursing.
67
+ */
68
+ export function walkTree(objects, treeSha, pathPrefix = "") {
69
+ const object = objects.get(treeSha);
70
+ if (!object) {
71
+ return Result.err(new ObjectDecodeError({
72
+ reason: "missing-tree-object",
73
+ message: `tree object not found: ${treeSha}`,
74
+ objectType: "tree",
75
+ sha: treeSha,
76
+ }));
77
+ }
78
+ const entries = parseTree(object.content);
79
+ if (entries.isErr())
80
+ return Result.err(entries.error);
81
+ const files = [];
82
+ for (const entry of entries.value) {
83
+ const path = pathPrefix ? `${pathPrefix}/${entry.name}` : entry.name;
84
+ if (entry.mode === TREE_MODE) {
85
+ const child = walkTree(objects, entry.sha, path);
86
+ if (child.isErr())
87
+ return Result.err(child.error);
88
+ files.push(...child.value);
89
+ }
90
+ else {
91
+ files.push({ mode: entry.mode, path, sha: entry.sha });
92
+ }
93
+ }
94
+ return Result.ok(files);
95
+ }
96
+ /**
97
+ * Resolve a slash-separated path inside a tree to the SHA of its leaf entry
98
+ * (usually a blob).
99
+ *
100
+ * Intermediate components must be subtrees; the final component may be any
101
+ * mode (blob, exec, symlink). Leading/trailing/duplicate slashes are tolerated.
102
+ *
103
+ * @returns Leaf SHA-1, or {@link PathNotFoundError} when any component is
104
+ * missing or an intermediate component is not a tree.
105
+ */
106
+ export function resolvePathToBlob(objects, treeSha, path) {
107
+ const parts = path.split("/").filter(Boolean);
108
+ if (parts.length === 0) {
109
+ return Result.err(new PathNotFoundError({
110
+ path,
111
+ treeSha,
112
+ message: `path not found: ${path}`,
113
+ }));
114
+ }
115
+ let currentTree = treeSha;
116
+ for (let i = 0; i < parts.length; i++) {
117
+ const object = objects.get(currentTree);
118
+ if (!object) {
119
+ return Result.err(new PathNotFoundError({
120
+ path,
121
+ treeSha: currentTree,
122
+ message: `path not found: ${path}`,
123
+ }));
124
+ }
125
+ const entries = parseTree(object.content);
126
+ if (entries.isErr())
127
+ return Result.err(entries.error);
128
+ const entry = entries.value.find((e) => e.name === parts[i]);
129
+ if (!entry) {
130
+ return Result.err(new PathNotFoundError({
131
+ path,
132
+ treeSha: currentTree,
133
+ message: `path not found: ${path}`,
134
+ }));
135
+ }
136
+ const isLast = i === parts.length - 1;
137
+ if (isLast)
138
+ return Result.ok(entry.sha);
139
+ if (entry.mode !== TREE_MODE) {
140
+ return Result.err(new PathNotFoundError({
141
+ path,
142
+ treeSha: currentTree,
143
+ message: `path not found: ${path}`,
144
+ }));
145
+ }
146
+ currentTree = entry.sha;
147
+ }
148
+ return Result.err(new PathNotFoundError({ path, treeSha, message: `path not found: ${path}` }));
149
+ }