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.
- package/AGENTS.md +177 -0
- package/LICENSE +21 -0
- package/README.md +247 -0
- package/esm/_dnt.shims.js +72 -0
- package/esm/cli.js +217 -0
- package/esm/client.js +439 -0
- package/esm/deps/jsr.io/@cliffy/command/1.1.0/_argument_types.js +1 -0
- package/esm/deps/jsr.io/@cliffy/command/1.1.0/_errors.js +133 -0
- package/esm/deps/jsr.io/@cliffy/command/1.1.0/_spread.js +1 -0
- package/esm/deps/jsr.io/@cliffy/command/1.1.0/_type_utils.js +1 -0
- package/esm/deps/jsr.io/@cliffy/command/1.1.0/_utils.js +141 -0
- package/esm/deps/jsr.io/@cliffy/command/1.1.0/command.js +1861 -0
- package/esm/deps/jsr.io/@cliffy/command/1.1.0/help/_help_generator.js +357 -0
- package/esm/deps/jsr.io/@cliffy/command/1.1.0/mod.js +13 -0
- package/esm/deps/jsr.io/@cliffy/command/1.1.0/type.js +27 -0
- package/esm/deps/jsr.io/@cliffy/command/1.1.0/types/action_list.js +16 -0
- package/esm/deps/jsr.io/@cliffy/command/1.1.0/types/boolean.js +13 -0
- package/esm/deps/jsr.io/@cliffy/command/1.1.0/types/child_command.js +14 -0
- package/esm/deps/jsr.io/@cliffy/command/1.1.0/types/command.js +9 -0
- package/esm/deps/jsr.io/@cliffy/command/1.1.0/types/enum.js +24 -0
- package/esm/deps/jsr.io/@cliffy/command/1.1.0/types/file.js +12 -0
- package/esm/deps/jsr.io/@cliffy/command/1.1.0/types/integer.js +9 -0
- package/esm/deps/jsr.io/@cliffy/command/1.1.0/types/number.js +9 -0
- package/esm/deps/jsr.io/@cliffy/command/1.1.0/types/secret.js +7 -0
- package/esm/deps/jsr.io/@cliffy/command/1.1.0/types/string.js +9 -0
- package/esm/deps/jsr.io/@cliffy/command/1.1.0/types.js +2 -0
- package/esm/deps/jsr.io/@cliffy/command/1.1.0/upgrade/_check_version.js +26 -0
- package/esm/deps/jsr.io/@cliffy/flags/1.1.0/_errors.js +129 -0
- package/esm/deps/jsr.io/@cliffy/flags/1.1.0/_utils.js +100 -0
- package/esm/deps/jsr.io/@cliffy/flags/1.1.0/_validate_flags.js +166 -0
- package/esm/deps/jsr.io/@cliffy/flags/1.1.0/flags.js +750 -0
- package/esm/deps/jsr.io/@cliffy/flags/1.1.0/mod.js +55 -0
- package/esm/deps/jsr.io/@cliffy/flags/1.1.0/types/boolean.js +11 -0
- package/esm/deps/jsr.io/@cliffy/flags/1.1.0/types/integer.js +9 -0
- package/esm/deps/jsr.io/@cliffy/flags/1.1.0/types/number.js +11 -0
- package/esm/deps/jsr.io/@cliffy/flags/1.1.0/types/string.js +4 -0
- package/esm/deps/jsr.io/@cliffy/flags/1.1.0/types.js +1 -0
- package/esm/deps/jsr.io/@cliffy/internal/1.1.0/runtime/exit.js +16 -0
- package/esm/deps/jsr.io/@cliffy/internal/1.1.0/runtime/get_args.js +11 -0
- package/esm/deps/jsr.io/@cliffy/internal/1.1.0/runtime/get_columns.js +25 -0
- package/esm/deps/jsr.io/@cliffy/internal/1.1.0/runtime/get_env.js +18 -0
- package/esm/deps/jsr.io/@cliffy/internal/1.1.0/runtime/inspect.js +11 -0
- package/esm/deps/jsr.io/@cliffy/table/1.1.0/_layout.js +616 -0
- package/esm/deps/jsr.io/@cliffy/table/1.1.0/_utils.js +79 -0
- package/esm/deps/jsr.io/@cliffy/table/1.1.0/border.js +18 -0
- package/esm/deps/jsr.io/@cliffy/table/1.1.0/cell.js +190 -0
- package/esm/deps/jsr.io/@cliffy/table/1.1.0/column.js +117 -0
- package/esm/deps/jsr.io/@cliffy/table/1.1.0/consume_words.js +64 -0
- package/esm/deps/jsr.io/@cliffy/table/1.1.0/mod.js +42 -0
- package/esm/deps/jsr.io/@cliffy/table/1.1.0/row.js +82 -0
- package/esm/deps/jsr.io/@cliffy/table/1.1.0/table.js +341 -0
- package/esm/deps/jsr.io/@cliffy/table/1.1.0/unicode_width.js +101 -0
- package/esm/deps/jsr.io/@std/crypto/1.1.0/_types.js +2 -0
- package/esm/deps/jsr.io/@std/crypto/1.1.0/_wasm/lib/deno_std_wasm_crypto.internal.js +237 -0
- package/esm/deps/jsr.io/@std/crypto/1.1.0/_wasm/lib/deno_std_wasm_crypto.js +2277 -0
- package/esm/deps/jsr.io/@std/crypto/1.1.0/_wasm/mod.js +46 -0
- package/esm/deps/jsr.io/@std/crypto/1.1.0/aes_gcm.js +132 -0
- package/esm/deps/jsr.io/@std/crypto/1.1.0/crypto.js +270 -0
- package/esm/deps/jsr.io/@std/crypto/1.1.0/mod.js +23 -0
- package/esm/deps/jsr.io/@std/crypto/1.1.0/timing_safe_equal.js +61 -0
- package/esm/deps/jsr.io/@std/encoding/1.0.10/_common16.js +51 -0
- package/esm/deps/jsr.io/@std/encoding/1.0.10/_common_detach.js +13 -0
- package/esm/deps/jsr.io/@std/encoding/1.0.10/_types.js +2 -0
- package/esm/deps/jsr.io/@std/encoding/1.0.10/hex.js +87 -0
- package/esm/deps/jsr.io/@std/fmt/1.0.10/colors.js +903 -0
- package/esm/deps/jsr.io/@std/text/1.0.18/closest_string.js +46 -0
- package/esm/deps/jsr.io/@std/text/1.0.18/levenshtein_distance.js +127 -0
- package/esm/errors.js +38 -0
- package/esm/index.js +10 -0
- package/esm/logger.js +216 -0
- package/esm/objects/commit.js +47 -0
- package/esm/objects/index.js +2 -0
- package/esm/objects/tree.js +149 -0
- package/esm/pack/delta.js +179 -0
- package/esm/pack/index.js +3 -0
- package/esm/pack/objects.js +72 -0
- package/esm/pack/parser.js +304 -0
- package/esm/package.json +3 -0
- package/esm/protocol/index.js +3 -0
- package/esm/protocol/pkt_line.js +103 -0
- package/esm/protocol/refs.js +100 -0
- package/esm/protocol/upload_pack.js +259 -0
- package/esm/transport.js +128 -0
- package/esm/types.js +8 -0
- package/package.json +50 -0
- package/types/_dnt.shims.d.ts +16 -0
- package/types/_dnt.shims.d.ts.map +1 -0
- package/types/cli.d.ts +3 -0
- package/types/cli.d.ts.map +1 -0
- package/types/client.d.ts +108 -0
- package/types/client.d.ts.map +1 -0
- package/types/deps/jsr.io/@cliffy/command/1.1.0/_argument_types.d.ts +163 -0
- package/types/deps/jsr.io/@cliffy/command/1.1.0/_argument_types.d.ts.map +1 -0
- package/types/deps/jsr.io/@cliffy/command/1.1.0/_errors.d.ts +71 -0
- package/types/deps/jsr.io/@cliffy/command/1.1.0/_errors.d.ts.map +1 -0
- package/types/deps/jsr.io/@cliffy/command/1.1.0/_spread.d.ts +16 -0
- package/types/deps/jsr.io/@cliffy/command/1.1.0/_spread.d.ts.map +1 -0
- package/types/deps/jsr.io/@cliffy/command/1.1.0/_type_utils.d.ts +15 -0
- package/types/deps/jsr.io/@cliffy/command/1.1.0/_type_utils.d.ts.map +1 -0
- package/types/deps/jsr.io/@cliffy/command/1.1.0/_utils.d.ts +38 -0
- package/types/deps/jsr.io/@cliffy/command/1.1.0/_utils.d.ts.map +1 -0
- package/types/deps/jsr.io/@cliffy/command/1.1.0/command.d.ts +1086 -0
- package/types/deps/jsr.io/@cliffy/command/1.1.0/command.d.ts.map +1 -0
- package/types/deps/jsr.io/@cliffy/command/1.1.0/help/_help_generator.d.ts +33 -0
- package/types/deps/jsr.io/@cliffy/command/1.1.0/help/_help_generator.d.ts.map +1 -0
- package/types/deps/jsr.io/@cliffy/command/1.1.0/mod.d.ts +78 -0
- package/types/deps/jsr.io/@cliffy/command/1.1.0/mod.d.ts.map +1 -0
- package/types/deps/jsr.io/@cliffy/command/1.1.0/type.d.ts +51 -0
- package/types/deps/jsr.io/@cliffy/command/1.1.0/type.d.ts.map +1 -0
- package/types/deps/jsr.io/@cliffy/command/1.1.0/types/action_list.d.ts +10 -0
- package/types/deps/jsr.io/@cliffy/command/1.1.0/types/action_list.d.ts.map +1 -0
- package/types/deps/jsr.io/@cliffy/command/1.1.0/types/boolean.d.ts +10 -0
- package/types/deps/jsr.io/@cliffy/command/1.1.0/types/boolean.d.ts.map +1 -0
- package/types/deps/jsr.io/@cliffy/command/1.1.0/types/child_command.d.ts +10 -0
- package/types/deps/jsr.io/@cliffy/command/1.1.0/types/child_command.d.ts.map +1 -0
- package/types/deps/jsr.io/@cliffy/command/1.1.0/types/command.d.ts +8 -0
- package/types/deps/jsr.io/@cliffy/command/1.1.0/types/command.d.ts.map +1 -0
- package/types/deps/jsr.io/@cliffy/command/1.1.0/types/enum.d.ts +11 -0
- package/types/deps/jsr.io/@cliffy/command/1.1.0/types/enum.d.ts.map +1 -0
- package/types/deps/jsr.io/@cliffy/command/1.1.0/types/file.d.ts +6 -0
- package/types/deps/jsr.io/@cliffy/command/1.1.0/types/file.d.ts.map +1 -0
- package/types/deps/jsr.io/@cliffy/command/1.1.0/types/integer.d.ts +8 -0
- package/types/deps/jsr.io/@cliffy/command/1.1.0/types/integer.d.ts.map +1 -0
- package/types/deps/jsr.io/@cliffy/command/1.1.0/types/number.d.ts +8 -0
- package/types/deps/jsr.io/@cliffy/command/1.1.0/types/number.d.ts.map +1 -0
- package/types/deps/jsr.io/@cliffy/command/1.1.0/types/secret.d.ts +6 -0
- package/types/deps/jsr.io/@cliffy/command/1.1.0/types/secret.d.ts.map +1 -0
- package/types/deps/jsr.io/@cliffy/command/1.1.0/types/string.d.ts +8 -0
- package/types/deps/jsr.io/@cliffy/command/1.1.0/types/string.d.ts.map +1 -0
- package/types/deps/jsr.io/@cliffy/command/1.1.0/types.d.ts +161 -0
- package/types/deps/jsr.io/@cliffy/command/1.1.0/types.d.ts.map +1 -0
- package/types/deps/jsr.io/@cliffy/command/1.1.0/upgrade/_check_version.d.ts +4 -0
- package/types/deps/jsr.io/@cliffy/command/1.1.0/upgrade/_check_version.d.ts.map +1 -0
- package/types/deps/jsr.io/@cliffy/flags/1.1.0/_errors.d.ts +67 -0
- package/types/deps/jsr.io/@cliffy/flags/1.1.0/_errors.d.ts.map +1 -0
- package/types/deps/jsr.io/@cliffy/flags/1.1.0/_utils.d.ts +17 -0
- package/types/deps/jsr.io/@cliffy/flags/1.1.0/_utils.d.ts.map +1 -0
- package/types/deps/jsr.io/@cliffy/flags/1.1.0/_validate_flags.d.ts +11 -0
- package/types/deps/jsr.io/@cliffy/flags/1.1.0/_validate_flags.d.ts.map +1 -0
- package/types/deps/jsr.io/@cliffy/flags/1.1.0/flags.d.ts +154 -0
- package/types/deps/jsr.io/@cliffy/flags/1.1.0/flags.d.ts.map +1 -0
- package/types/deps/jsr.io/@cliffy/flags/1.1.0/mod.d.ts +57 -0
- package/types/deps/jsr.io/@cliffy/flags/1.1.0/mod.d.ts.map +1 -0
- package/types/deps/jsr.io/@cliffy/flags/1.1.0/types/boolean.d.ts +4 -0
- package/types/deps/jsr.io/@cliffy/flags/1.1.0/types/boolean.d.ts.map +1 -0
- package/types/deps/jsr.io/@cliffy/flags/1.1.0/types/integer.d.ts +4 -0
- package/types/deps/jsr.io/@cliffy/flags/1.1.0/types/integer.d.ts.map +1 -0
- package/types/deps/jsr.io/@cliffy/flags/1.1.0/types/number.d.ts +4 -0
- package/types/deps/jsr.io/@cliffy/flags/1.1.0/types/number.d.ts.map +1 -0
- package/types/deps/jsr.io/@cliffy/flags/1.1.0/types/string.d.ts +4 -0
- package/types/deps/jsr.io/@cliffy/flags/1.1.0/types/string.d.ts.map +1 -0
- package/types/deps/jsr.io/@cliffy/flags/1.1.0/types.d.ts +170 -0
- package/types/deps/jsr.io/@cliffy/flags/1.1.0/types.d.ts.map +1 -0
- package/types/deps/jsr.io/@cliffy/internal/1.1.0/runtime/exit.d.ts +8 -0
- package/types/deps/jsr.io/@cliffy/internal/1.1.0/runtime/exit.d.ts.map +1 -0
- package/types/deps/jsr.io/@cliffy/internal/1.1.0/runtime/get_args.d.ts +7 -0
- package/types/deps/jsr.io/@cliffy/internal/1.1.0/runtime/get_args.d.ts.map +1 -0
- package/types/deps/jsr.io/@cliffy/internal/1.1.0/runtime/get_columns.d.ts +7 -0
- package/types/deps/jsr.io/@cliffy/internal/1.1.0/runtime/get_columns.d.ts.map +1 -0
- package/types/deps/jsr.io/@cliffy/internal/1.1.0/runtime/get_env.d.ts +8 -0
- package/types/deps/jsr.io/@cliffy/internal/1.1.0/runtime/get_env.d.ts.map +1 -0
- package/types/deps/jsr.io/@cliffy/internal/1.1.0/runtime/inspect.d.ts +7 -0
- package/types/deps/jsr.io/@cliffy/internal/1.1.0/runtime/inspect.d.ts.map +1 -0
- package/types/deps/jsr.io/@cliffy/table/1.1.0/_layout.d.ts +108 -0
- package/types/deps/jsr.io/@cliffy/table/1.1.0/_layout.d.ts.map +1 -0
- package/types/deps/jsr.io/@cliffy/table/1.1.0/_utils.d.ts +26 -0
- package/types/deps/jsr.io/@cliffy/table/1.1.0/_utils.d.ts.map +1 -0
- package/types/deps/jsr.io/@cliffy/table/1.1.0/border.d.ts +21 -0
- package/types/deps/jsr.io/@cliffy/table/1.1.0/border.d.ts.map +1 -0
- package/types/deps/jsr.io/@cliffy/table/1.1.0/cell.d.ts +155 -0
- package/types/deps/jsr.io/@cliffy/table/1.1.0/cell.d.ts.map +1 -0
- package/types/deps/jsr.io/@cliffy/table/1.1.0/column.d.ts +97 -0
- package/types/deps/jsr.io/@cliffy/table/1.1.0/column.d.ts.map +1 -0
- package/types/deps/jsr.io/@cliffy/table/1.1.0/consume_words.d.ts +30 -0
- package/types/deps/jsr.io/@cliffy/table/1.1.0/consume_words.d.ts.map +1 -0
- package/types/deps/jsr.io/@cliffy/table/1.1.0/mod.d.ts +43 -0
- package/types/deps/jsr.io/@cliffy/table/1.1.0/mod.d.ts.map +1 -0
- package/types/deps/jsr.io/@cliffy/table/1.1.0/row.d.ts +67 -0
- package/types/deps/jsr.io/@cliffy/table/1.1.0/row.d.ts.map +1 -0
- package/types/deps/jsr.io/@cliffy/table/1.1.0/table.d.ts +235 -0
- package/types/deps/jsr.io/@cliffy/table/1.1.0/table.d.ts.map +1 -0
- package/types/deps/jsr.io/@cliffy/table/1.1.0/unicode_width.d.ts +40 -0
- package/types/deps/jsr.io/@cliffy/table/1.1.0/unicode_width.d.ts.map +1 -0
- package/types/deps/jsr.io/@std/crypto/1.1.0/_types.d.ts +9 -0
- package/types/deps/jsr.io/@std/crypto/1.1.0/_types.d.ts.map +1 -0
- package/types/deps/jsr.io/@std/crypto/1.1.0/_wasm/lib/deno_std_wasm_crypto.d.ts +2 -0
- package/types/deps/jsr.io/@std/crypto/1.1.0/_wasm/lib/deno_std_wasm_crypto.d.ts.map +1 -0
- package/types/deps/jsr.io/@std/crypto/1.1.0/_wasm/lib/deno_std_wasm_crypto.internal.d.ts +69 -0
- package/types/deps/jsr.io/@std/crypto/1.1.0/_wasm/lib/deno_std_wasm_crypto.internal.d.ts.map +1 -0
- package/types/deps/jsr.io/@std/crypto/1.1.0/_wasm/mod.d.ts +13 -0
- package/types/deps/jsr.io/@std/crypto/1.1.0/_wasm/mod.d.ts.map +1 -0
- package/types/deps/jsr.io/@std/crypto/1.1.0/aes_gcm.d.ts +76 -0
- package/types/deps/jsr.io/@std/crypto/1.1.0/aes_gcm.d.ts.map +1 -0
- package/types/deps/jsr.io/@std/crypto/1.1.0/crypto.d.ts +149 -0
- package/types/deps/jsr.io/@std/crypto/1.1.0/crypto.d.ts.map +1 -0
- package/types/deps/jsr.io/@std/crypto/1.1.0/mod.d.ts +22 -0
- package/types/deps/jsr.io/@std/crypto/1.1.0/mod.d.ts.map +1 -0
- package/types/deps/jsr.io/@std/crypto/1.1.0/timing_safe_equal.d.ts +40 -0
- package/types/deps/jsr.io/@std/crypto/1.1.0/timing_safe_equal.d.ts.map +1 -0
- package/types/deps/jsr.io/@std/encoding/1.0.10/_common16.d.ts +23 -0
- package/types/deps/jsr.io/@std/encoding/1.0.10/_common16.d.ts.map +1 -0
- package/types/deps/jsr.io/@std/encoding/1.0.10/_common_detach.d.ts +4 -0
- package/types/deps/jsr.io/@std/encoding/1.0.10/_common_detach.d.ts.map +1 -0
- package/types/deps/jsr.io/@std/encoding/1.0.10/_types.d.ts +9 -0
- package/types/deps/jsr.io/@std/encoding/1.0.10/_types.d.ts.map +1 -0
- package/types/deps/jsr.io/@std/encoding/1.0.10/hex.d.ts +39 -0
- package/types/deps/jsr.io/@std/encoding/1.0.10/hex.d.ts.map +1 -0
- package/types/deps/jsr.io/@std/fmt/1.0.10/colors.d.ts +700 -0
- package/types/deps/jsr.io/@std/fmt/1.0.10/colors.d.ts.map +1 -0
- package/types/deps/jsr.io/@std/text/1.0.18/closest_string.d.ts +42 -0
- package/types/deps/jsr.io/@std/text/1.0.18/closest_string.d.ts.map +1 -0
- package/types/deps/jsr.io/@std/text/1.0.18/levenshtein_distance.d.ts +23 -0
- package/types/deps/jsr.io/@std/text/1.0.18/levenshtein_distance.d.ts.map +1 -0
- package/types/errors.d.ts +73 -0
- package/types/errors.d.ts.map +1 -0
- package/types/index.d.ts +14 -0
- package/types/index.d.ts.map +1 -0
- package/types/logger.d.ts +70 -0
- package/types/logger.d.ts.map +1 -0
- package/types/objects/commit.d.ts +23 -0
- package/types/objects/commit.d.ts.map +1 -0
- package/types/objects/index.d.ts +3 -0
- package/types/objects/index.d.ts.map +1 -0
- package/types/objects/tree.d.ts +49 -0
- package/types/objects/tree.d.ts.map +1 -0
- package/types/pack/delta.d.ts +47 -0
- package/types/pack/delta.d.ts.map +1 -0
- package/types/pack/index.d.ts +4 -0
- package/types/pack/index.d.ts.map +1 -0
- package/types/pack/objects.d.ts +53 -0
- package/types/pack/objects.d.ts.map +1 -0
- package/types/pack/parser.d.ts +61 -0
- package/types/pack/parser.d.ts.map +1 -0
- package/types/protocol/index.d.ts +4 -0
- package/types/protocol/index.d.ts.map +1 -0
- package/types/protocol/pkt_line.d.ts +44 -0
- package/types/protocol/pkt_line.d.ts.map +1 -0
- package/types/protocol/refs.d.ts +40 -0
- package/types/protocol/refs.d.ts.map +1 -0
- package/types/protocol/upload_pack.d.ts +45 -0
- package/types/protocol/upload_pack.d.ts.map +1 -0
- package/types/transport.d.ts +24 -0
- package/types/transport.d.ts.map +1 -0
- package/types/types.d.ts +121 -0
- package/types/types.d.ts.map +1 -0
package/esm/cli.js
ADDED
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import * as dntShim from "./_dnt.shims.js";
|
|
3
|
+
!/usr/bin / env - S;
|
|
4
|
+
deno;
|
|
5
|
+
run--;
|
|
6
|
+
allow - net;
|
|
7
|
+
/**
|
|
8
|
+
* @module cli
|
|
9
|
+
*
|
|
10
|
+
* Cliffy-driven command-line front-end. Each subcommand wires the global
|
|
11
|
+
* verbosity / stats flags into a freshly built {@link RemoteGit}, executes
|
|
12
|
+
* one operation, and prints a plain-text result to stdout. Errors come back
|
|
13
|
+
* through {@link unwrap} as `[Tag] message` lines on stderr with exit 1.
|
|
14
|
+
*
|
|
15
|
+
* Subcommands:
|
|
16
|
+
* - `probe` — capability + filter probe
|
|
17
|
+
* - `ls-refs` — list advertised refs
|
|
18
|
+
* - `cat-commit` — fetch one commit
|
|
19
|
+
* - `cat-tree` — fetch a commit's root tree (or a tree by sha)
|
|
20
|
+
* - `list-files` — walk a snapshot and emit every file path
|
|
21
|
+
* - `cat-blob` — pipe one blob's raw bytes to stdout
|
|
22
|
+
*/
|
|
23
|
+
import { Command, ValidationError } from "./deps/jsr.io/@cliffy/command/1.1.0/mod.js";
|
|
24
|
+
import { Logger, RemoteGit } from "./index.js";
|
|
25
|
+
import { parseTree } from "./objects/index.js";
|
|
26
|
+
/** Bumped in lockstep with `deno.json`. Surfaced by `git-remote-ops --version`. */
|
|
27
|
+
const VERSION = "0.1.0";
|
|
28
|
+
/** Octal mode of a subtree entry — used to recurse during `list-files`. */
|
|
29
|
+
const TREE_MODE = "40000";
|
|
30
|
+
function resolveLevel(flags) {
|
|
31
|
+
if (flags.quiet)
|
|
32
|
+
return "silent";
|
|
33
|
+
if (flags.debug)
|
|
34
|
+
return "trace";
|
|
35
|
+
if (flags.verbose)
|
|
36
|
+
return "debug";
|
|
37
|
+
return "info";
|
|
38
|
+
}
|
|
39
|
+
function makeClient(url, flags) {
|
|
40
|
+
const logger = new Logger({
|
|
41
|
+
level: resolveLevel(flags),
|
|
42
|
+
sink: (line) => console.error(line),
|
|
43
|
+
}, "client");
|
|
44
|
+
const client = new RemoteGit(url, { logger });
|
|
45
|
+
return { client, logger };
|
|
46
|
+
}
|
|
47
|
+
function unwrap(result) {
|
|
48
|
+
if (result.isErr()) {
|
|
49
|
+
console.error(`[${result.error._tag}] ${result.error.message}`);
|
|
50
|
+
dntShim.Deno.exit(1);
|
|
51
|
+
}
|
|
52
|
+
return result.value;
|
|
53
|
+
}
|
|
54
|
+
function maybeStats(logger, flags) {
|
|
55
|
+
if (flags.stats)
|
|
56
|
+
console.error(logger.summary());
|
|
57
|
+
}
|
|
58
|
+
function cachedTreeEntries(client, treeSha) {
|
|
59
|
+
const object = client.getObject(treeSha);
|
|
60
|
+
if (!object) {
|
|
61
|
+
console.error(`[ObjectNotFoundError] tree not present in fetched pack: ${treeSha}`);
|
|
62
|
+
dntShim.Deno.exit(1);
|
|
63
|
+
}
|
|
64
|
+
if (object.type !== "tree") {
|
|
65
|
+
console.error(`[ObjectDecodeError] object is not a tree: ${treeSha}`);
|
|
66
|
+
dntShim.Deno.exit(1);
|
|
67
|
+
}
|
|
68
|
+
const entries = parseTree(object.content);
|
|
69
|
+
if (entries.isErr()) {
|
|
70
|
+
console.error(`[${entries.error._tag}] ${entries.error.message}`);
|
|
71
|
+
dntShim.Deno.exit(1);
|
|
72
|
+
}
|
|
73
|
+
return entries.value;
|
|
74
|
+
}
|
|
75
|
+
function printCachedFiles(client, entries, prefix, details) {
|
|
76
|
+
for (const entry of entries) {
|
|
77
|
+
const path = prefix ? `${prefix}/${entry.name}` : entry.name;
|
|
78
|
+
if (entry.mode === TREE_MODE) {
|
|
79
|
+
printCachedFiles(client, cachedTreeEntries(client, entry.sha), path, details);
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
console.log(details ? `${entry.mode} ${entry.sha} ${path}` : path);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
await new Command()
|
|
87
|
+
.name("git-remote-ops")
|
|
88
|
+
.version(VERSION)
|
|
89
|
+
.description("Read-only Git remote operations over smart HTTP.")
|
|
90
|
+
.globalOption("-q, --quiet", "Suppress all log output (silent level).")
|
|
91
|
+
.globalOption("-v, --verbose", "Enable debug-level logs to stderr.", {
|
|
92
|
+
conflicts: ["quiet"],
|
|
93
|
+
})
|
|
94
|
+
.globalOption("--debug", "Enable trace-level logs to stderr (very chatty).", {
|
|
95
|
+
conflicts: ["quiet"],
|
|
96
|
+
})
|
|
97
|
+
.globalOption("--stats", "Print performance/analytics summary on stderr after completion.")
|
|
98
|
+
.action(function () {
|
|
99
|
+
this.showHelp();
|
|
100
|
+
})
|
|
101
|
+
.command("probe", "Probe server capabilities (protocol, filter, shallow).")
|
|
102
|
+
.arguments("<url:string>")
|
|
103
|
+
.action(async (flags, url) => {
|
|
104
|
+
const { client, logger } = makeClient(url, flags);
|
|
105
|
+
const profile = unwrap(await client.probe(true));
|
|
106
|
+
console.log(`refs=${profile.refs.size}`);
|
|
107
|
+
console.log(`protocol=${profile.protocolVersion}`);
|
|
108
|
+
console.log(`filter_blob_none=${profile.supportsFilterBlobNone}`);
|
|
109
|
+
console.log(`filter_tree_0=${profile.supportsFilterTree0}`);
|
|
110
|
+
console.log(`shallow=${profile.supportsShallow}`);
|
|
111
|
+
maybeStats(logger, flags);
|
|
112
|
+
})
|
|
113
|
+
.command("ls-refs", "List remote refs as '<sha> <name>' per line.")
|
|
114
|
+
.arguments("<url:string>")
|
|
115
|
+
.action(async (flags, url) => {
|
|
116
|
+
const { client, logger } = makeClient(url, flags);
|
|
117
|
+
for (const [name, sha] of unwrap(await client.lsRefs())) {
|
|
118
|
+
console.log(`${sha} ${name}`);
|
|
119
|
+
}
|
|
120
|
+
maybeStats(logger, flags);
|
|
121
|
+
})
|
|
122
|
+
.command("cat-commit", "Fetch & print a commit object.")
|
|
123
|
+
.arguments("<url:string>")
|
|
124
|
+
.option("--ref <ref:string>", "Ref or sha to resolve.", { default: "HEAD" })
|
|
125
|
+
.option("--depth <n:integer>", "Shallow depth (ignored if server lacks shallow).", {
|
|
126
|
+
default: 1,
|
|
127
|
+
value: (n) => {
|
|
128
|
+
if (n < 1)
|
|
129
|
+
throw new ValidationError("--depth must be >= 1");
|
|
130
|
+
return n;
|
|
131
|
+
},
|
|
132
|
+
})
|
|
133
|
+
.option("--filter <spec:string>", "Object filter spec (e.g. blob:none, tree:0).", {
|
|
134
|
+
default: "blob:none",
|
|
135
|
+
})
|
|
136
|
+
.option("--no-filter", "Fetch without object filter; may download a full snapshot pack.")
|
|
137
|
+
.action(async (flags, url) => {
|
|
138
|
+
const { client, logger } = makeClient(url, flags);
|
|
139
|
+
const filter = flags.filter === false ? undefined : flags.filter;
|
|
140
|
+
const { sha, commit } = unwrap(await client.fetchCommit(flags.ref, { depth: flags.depth, filter }));
|
|
141
|
+
console.log(`commit ${sha}`);
|
|
142
|
+
console.log(`tree ${commit.tree}`);
|
|
143
|
+
if (commit.parent)
|
|
144
|
+
console.log(`parent ${commit.parent}`);
|
|
145
|
+
if (commit.author)
|
|
146
|
+
console.log(`author ${commit.author}`);
|
|
147
|
+
if (commit.committer)
|
|
148
|
+
console.log(`committer ${commit.committer}`);
|
|
149
|
+
maybeStats(logger, flags);
|
|
150
|
+
})
|
|
151
|
+
.command("cat-tree", "Fetch a commit snapshot and print its root tree as '<mode> <sha> <name>' per entry.")
|
|
152
|
+
.arguments("<url:string>")
|
|
153
|
+
.option("--ref <ref:string>", "Ref or commit sha to resolve.", { default: "HEAD" })
|
|
154
|
+
.option("--depth <n:integer>", "Shallow depth (ignored if server lacks shallow).", {
|
|
155
|
+
default: 1,
|
|
156
|
+
value: (n) => {
|
|
157
|
+
if (n < 1)
|
|
158
|
+
throw new ValidationError("--depth must be >= 1");
|
|
159
|
+
return n;
|
|
160
|
+
},
|
|
161
|
+
})
|
|
162
|
+
.option("--filter <spec:string>", "Object filter spec (e.g. blob:none, tree:0).", {
|
|
163
|
+
default: "blob:none",
|
|
164
|
+
})
|
|
165
|
+
.option("--no-filter", "Fetch without object filter; downloads a full snapshot pack.")
|
|
166
|
+
.option("--tree-sha <sha:string>", "Fetch the tree by SHA directly (no commit snapshot).")
|
|
167
|
+
.action(async (flags, url) => {
|
|
168
|
+
const { client, logger } = makeClient(url, flags);
|
|
169
|
+
if (flags.treeSha) {
|
|
170
|
+
for (const entry of unwrap(await client.fetchTree(flags.treeSha))) {
|
|
171
|
+
console.log(`${entry.mode} ${entry.sha} ${entry.name}`);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
else {
|
|
175
|
+
const filter = flags.filter === false ? undefined : flags.filter;
|
|
176
|
+
const result = unwrap(await client.fetchTreeForCommit(flags.ref, { depth: flags.depth, filter }));
|
|
177
|
+
for (const entry of result.entries) {
|
|
178
|
+
console.log(`${entry.mode} ${entry.sha} ${entry.name}`);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
maybeStats(logger, flags);
|
|
182
|
+
})
|
|
183
|
+
.command("list-files", "List all files in a commit snapshot without cloning.")
|
|
184
|
+
.arguments("<url:string>")
|
|
185
|
+
.option("--ref <ref:string>", "Ref or commit sha to resolve.", { default: "HEAD" })
|
|
186
|
+
.option("--depth <n:integer>", "Shallow depth (ignored if server lacks shallow).", {
|
|
187
|
+
default: 1,
|
|
188
|
+
value: (n) => {
|
|
189
|
+
if (n < 1)
|
|
190
|
+
throw new ValidationError("--depth must be >= 1");
|
|
191
|
+
return n;
|
|
192
|
+
},
|
|
193
|
+
})
|
|
194
|
+
.option("--filter <spec:string>", "Object filter spec (e.g. blob:none, tree:0).", {
|
|
195
|
+
default: "blob:none",
|
|
196
|
+
})
|
|
197
|
+
.option("--no-filter", "Fetch without object filter; downloads a full snapshot pack.")
|
|
198
|
+
.option("--details", "Print '<mode> <sha> <path>' instead of path only.")
|
|
199
|
+
.action(async (flags, url) => {
|
|
200
|
+
const { client, logger } = makeClient(url, flags);
|
|
201
|
+
const filter = flags.filter === false ? undefined : flags.filter;
|
|
202
|
+
const result = unwrap(await client.fetchTreeForCommit(flags.ref, {
|
|
203
|
+
depth: flags.depth,
|
|
204
|
+
filter,
|
|
205
|
+
parseFull: true,
|
|
206
|
+
}));
|
|
207
|
+
printCachedFiles(client, result.entries, "", !!flags.details);
|
|
208
|
+
maybeStats(logger, flags);
|
|
209
|
+
})
|
|
210
|
+
.command("cat-blob", "Fetch a blob and write raw bytes to stdout.")
|
|
211
|
+
.arguments("<url:string> <blob-sha:string>")
|
|
212
|
+
.action(async (flags, url, sha) => {
|
|
213
|
+
const { client, logger } = makeClient(url, flags);
|
|
214
|
+
await dntShim.Deno.stdout.write(unwrap(await client.fetchBlob(sha)));
|
|
215
|
+
maybeStats(logger, flags);
|
|
216
|
+
})
|
|
217
|
+
.parse(dntShim.Deno.args);
|
package/esm/client.js
ADDED
|
@@ -0,0 +1,439 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module client
|
|
3
|
+
*
|
|
4
|
+
* High-level read-only Git remote client. {@link RemoteGit} composes the
|
|
5
|
+
* transport, protocol, and pack layers behind a small surface: discover a
|
|
6
|
+
* server, ask for commits/trees/blobs, get them back as decoded objects.
|
|
7
|
+
*
|
|
8
|
+
* The client caches:
|
|
9
|
+
* - a single {@link ServerProfile} (capabilities + ref advertisement);
|
|
10
|
+
* - every object materialized by any prior fetch, in a process-lifetime
|
|
11
|
+
* map. Subsequent fetches dedupe wants against this map so multi-step
|
|
12
|
+
* workflows (commit → tree → blob) only pay for what's new.
|
|
13
|
+
*/
|
|
14
|
+
import { Result } from "better-result";
|
|
15
|
+
import { ObjectDecodeError, ObjectNotFoundError, RefNotFoundError } from "./errors.js";
|
|
16
|
+
import { Logger } from "./logger.js";
|
|
17
|
+
import { parseCommit, parseTree } from "./objects/index.js";
|
|
18
|
+
import { parsePackfile } from "./pack/index.js";
|
|
19
|
+
import { buildFetchRequest, extractPack, parseRefAdvertisement, parseV2CapabilityAdvertisement, } from "./protocol/index.js";
|
|
20
|
+
import { getSmartHttp, postUploadPack } from "./transport.js";
|
|
21
|
+
/**
|
|
22
|
+
* Capabilities offered on every v0 fetch unless overridden. `shallow` and
|
|
23
|
+
* `filter` are appended dynamically when the corresponding feature is in use.
|
|
24
|
+
*
|
|
25
|
+
* - `multi_ack`: lets the server send `ACK` continuations while we're
|
|
26
|
+
* negotiating wants. Required by some servers even on a `done` short-circuit.
|
|
27
|
+
* - `side-band-64k`: turns on the channel-multiplexed response framing
|
|
28
|
+
* ({@link demuxSideband} expects this).
|
|
29
|
+
* - `ofs-delta`: allows the more compact offset-based delta encoding.
|
|
30
|
+
* - `agent=…`: identification string, mirrored into v2's `agent=` header.
|
|
31
|
+
*/
|
|
32
|
+
const DEFAULT_CAPS = [
|
|
33
|
+
"multi_ack",
|
|
34
|
+
"side-band-64k",
|
|
35
|
+
"ofs-delta",
|
|
36
|
+
"agent=git-remote-ops-deno/0.1",
|
|
37
|
+
];
|
|
38
|
+
function fail(error) {
|
|
39
|
+
return Result.err(error);
|
|
40
|
+
}
|
|
41
|
+
function buildCaps(opts = {}) {
|
|
42
|
+
return [
|
|
43
|
+
...DEFAULT_CAPS,
|
|
44
|
+
...(opts.shallow ? ["shallow"] : []),
|
|
45
|
+
...(opts.filter ? ["filter"] : []),
|
|
46
|
+
];
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Read-only Git remote client over smart HTTP.
|
|
50
|
+
*
|
|
51
|
+
* One instance corresponds to one upstream `url`. Reuse it across operations
|
|
52
|
+
* so the cached profile and object store can do their job; throw it away
|
|
53
|
+
* when you're done with that remote.
|
|
54
|
+
*/
|
|
55
|
+
export class RemoteGit {
|
|
56
|
+
url;
|
|
57
|
+
logger;
|
|
58
|
+
profile = null;
|
|
59
|
+
objects = new Map();
|
|
60
|
+
snapshotCommits = new Set();
|
|
61
|
+
diagnostic;
|
|
62
|
+
transportLogger;
|
|
63
|
+
packLogger;
|
|
64
|
+
/**
|
|
65
|
+
* @param url Base repository URL (e.g. `https://github.com/owner/repo.git`).
|
|
66
|
+
* Trailing slashes are stripped.
|
|
67
|
+
* @param options Optional logger / diagnostic sink. When neither is given, a
|
|
68
|
+
* silent logger is used. Passing `diagnostic` alone gets you a `debug`
|
|
69
|
+
* logger routed to the diagnostic function.
|
|
70
|
+
*/
|
|
71
|
+
constructor(url, options) {
|
|
72
|
+
this.url = url.replace(/\/+$/, "");
|
|
73
|
+
this.diagnostic = options?.diagnostic;
|
|
74
|
+
this.logger = options?.logger ??
|
|
75
|
+
new Logger({
|
|
76
|
+
level: options?.diagnostic ? "debug" : "silent",
|
|
77
|
+
sink: options?.diagnostic,
|
|
78
|
+
}, "client");
|
|
79
|
+
this.transportLogger = this.logger.child("transport");
|
|
80
|
+
this.packLogger = this.logger.child("pack");
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Fetch and cache the server's ref/capability advertisement.
|
|
84
|
+
*
|
|
85
|
+
* Issues both a v0/v1 GET and a v2 GET. The v0 response is the source of
|
|
86
|
+
* truth for refs and base capabilities; if the v2 response advertises
|
|
87
|
+
* `version=2`, those caps are merged in and `protocolVersion` flips to 2.
|
|
88
|
+
* Subsequent client calls hit the cached profile until `discover()` runs again.
|
|
89
|
+
*/
|
|
90
|
+
async discover() {
|
|
91
|
+
this.logger.info(`discover ${this.url}`);
|
|
92
|
+
const response = await getSmartHttp(this.url, "/info/refs?service=git-upload-pack", {
|
|
93
|
+
logger: this.transportLogger,
|
|
94
|
+
});
|
|
95
|
+
if (response.isErr())
|
|
96
|
+
return fail(response.error);
|
|
97
|
+
const parsed = parseRefAdvertisement(response.value.body);
|
|
98
|
+
if (parsed.isErr())
|
|
99
|
+
return fail(parsed.error);
|
|
100
|
+
let protocolVersion = 0;
|
|
101
|
+
const capabilities = new Set(parsed.value.capabilities);
|
|
102
|
+
const v2Response = await getSmartHttp(this.url, "/info/refs?service=git-upload-pack", {
|
|
103
|
+
protocolVersion: 2,
|
|
104
|
+
logger: this.transportLogger,
|
|
105
|
+
});
|
|
106
|
+
if (v2Response.isOk()) {
|
|
107
|
+
const parsedV2 = parseV2CapabilityAdvertisement(v2Response.value.body);
|
|
108
|
+
if (parsedV2.isOk() && parsedV2.value.has("version=2")) {
|
|
109
|
+
protocolVersion = 2;
|
|
110
|
+
for (const cap of parsedV2.value)
|
|
111
|
+
capabilities.add(cap);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
this.profile = {
|
|
115
|
+
url: this.url,
|
|
116
|
+
refs: parsed.value.refs,
|
|
117
|
+
advertisedCaps: capabilities,
|
|
118
|
+
protocolVersion,
|
|
119
|
+
supportsFilterBlobNone: false,
|
|
120
|
+
supportsFilterTree0: false,
|
|
121
|
+
supportsShallow: capabilities.has("shallow"),
|
|
122
|
+
probed: false,
|
|
123
|
+
};
|
|
124
|
+
return Result.ok(this.profile);
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Confirm what the server *actually* honours, not just what it advertises.
|
|
128
|
+
*
|
|
129
|
+
* Some servers advertise `filter` but ignore it; others advertise `shallow`
|
|
130
|
+
* but reject deepen requests. We send minimal-cost shallow fetches with
|
|
131
|
+
* `blob:none` and `tree:0` filters and look at the returned pack to decide.
|
|
132
|
+
* Sets `supportsFilterBlobNone` / `supportsFilterTree0` on the cached profile.
|
|
133
|
+
*
|
|
134
|
+
* @param verbose Emit raw probe outcomes through {@link RemoteGitOptions.diagnostic}.
|
|
135
|
+
*/
|
|
136
|
+
async probe(verbose = false) {
|
|
137
|
+
const profileResult = this.profile ? Result.ok(this.profile) : await this.discover();
|
|
138
|
+
if (profileResult.isErr())
|
|
139
|
+
return fail(profileResult.error);
|
|
140
|
+
const profile = profileResult.value;
|
|
141
|
+
if (profile.probed) {
|
|
142
|
+
return Result.ok(profile);
|
|
143
|
+
}
|
|
144
|
+
profile.supportsShallow = profile.advertisedCaps.has("shallow");
|
|
145
|
+
const advertisesFilter = profile.advertisedCaps.has("filter");
|
|
146
|
+
const targetSha = this.pickProbeSha(profile);
|
|
147
|
+
if (targetSha.isErr())
|
|
148
|
+
return fail(targetSha.error);
|
|
149
|
+
if (advertisesFilter) {
|
|
150
|
+
await this.probeFilter(profile, targetSha.value, verbose);
|
|
151
|
+
}
|
|
152
|
+
profile.probed = true;
|
|
153
|
+
return Result.ok(profile);
|
|
154
|
+
}
|
|
155
|
+
/** Return a snapshot of the advertised refs (name → sha). Triggers `discover()` if needed. */
|
|
156
|
+
async lsRefs() {
|
|
157
|
+
const profile = this.profile ? Result.ok(this.profile) : await this.discover();
|
|
158
|
+
if (profile.isErr())
|
|
159
|
+
return fail(profile.error);
|
|
160
|
+
return Result.ok(new Map(profile.value.refs));
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Resolve `ref` to a 40-char hex sha against the cached advertisement.
|
|
164
|
+
*
|
|
165
|
+
* Tries the literal name, then `refs/heads/<ref>`, then `refs/tags/<ref>`.
|
|
166
|
+
* A 40-char hex input is accepted as-is even when it isn't in the ad — the
|
|
167
|
+
* server may still serve it as a `want`.
|
|
168
|
+
*/
|
|
169
|
+
async resolveRef(ref) {
|
|
170
|
+
const profile = this.profile ? Result.ok(this.profile) : await this.discover();
|
|
171
|
+
if (profile.isErr())
|
|
172
|
+
return fail(profile.error);
|
|
173
|
+
for (const candidate of [ref, `refs/heads/${ref}`, `refs/tags/${ref}`]) {
|
|
174
|
+
const sha = profile.value.refs.get(candidate);
|
|
175
|
+
if (sha) {
|
|
176
|
+
return Result.ok(sha);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
if (/^[0-9a-f]{40}$/.test(ref)) {
|
|
180
|
+
return Result.ok(ref);
|
|
181
|
+
}
|
|
182
|
+
return fail(new RefNotFoundError({ ref, message: `ref not found: ${ref}` }));
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Fetch and decode a single commit reachable from `ref`.
|
|
186
|
+
*
|
|
187
|
+
* `options.depth` defaults to deep fetch; pass `1` for a snapshot. Filters
|
|
188
|
+
* are silently dropped if the server doesn't support them — we log an
|
|
189
|
+
* info-level message in that case.
|
|
190
|
+
*/
|
|
191
|
+
async fetchCommit(ref, options = {}) {
|
|
192
|
+
const profile = this.profile ? Result.ok(this.profile) : await this.discover();
|
|
193
|
+
if (profile.isErr())
|
|
194
|
+
return fail(profile.error);
|
|
195
|
+
const commitSha = await this.resolveRef(ref);
|
|
196
|
+
if (commitSha.isErr())
|
|
197
|
+
return fail(commitSha.error);
|
|
198
|
+
const depth = options.depth !== undefined && profile.value.supportsShallow
|
|
199
|
+
? options.depth
|
|
200
|
+
: undefined;
|
|
201
|
+
const filter = options.filter !== undefined && profile.value.advertisedCaps.has("filter")
|
|
202
|
+
? options.filter
|
|
203
|
+
: undefined;
|
|
204
|
+
if (options.filter !== undefined && filter === undefined) {
|
|
205
|
+
this.logger.info(`server does not advertise object filters; fetching without ${options.filter}`);
|
|
206
|
+
}
|
|
207
|
+
const objects = await this.fetchObjects([commitSha.value], depth, filter, options.parseFull === true);
|
|
208
|
+
if (objects.isErr())
|
|
209
|
+
return fail(objects.error);
|
|
210
|
+
const object = requiredObject(objects.value, commitSha.value);
|
|
211
|
+
if (object.isErr())
|
|
212
|
+
return fail(object.error);
|
|
213
|
+
if (object.value.type !== "commit") {
|
|
214
|
+
return fail(new ObjectDecodeError({
|
|
215
|
+
reason: "unexpected-object-type",
|
|
216
|
+
message: `object is not a commit: ${commitSha.value}`,
|
|
217
|
+
objectType: object.value.type,
|
|
218
|
+
sha: commitSha.value,
|
|
219
|
+
}));
|
|
220
|
+
}
|
|
221
|
+
const commit = parseCommit(object.value.content);
|
|
222
|
+
if (commit.isErr())
|
|
223
|
+
return fail(commit.error);
|
|
224
|
+
return Result.ok({ commit: commit.value, sha: commitSha.value });
|
|
225
|
+
}
|
|
226
|
+
/** Fetch a blob by sha and return its raw contents. */
|
|
227
|
+
async fetchBlob(sha) {
|
|
228
|
+
const profile = this.profile ? Result.ok(this.profile) : await this.discover();
|
|
229
|
+
if (profile.isErr())
|
|
230
|
+
return fail(profile.error);
|
|
231
|
+
const objects = await this.fetchObjects([sha]);
|
|
232
|
+
if (objects.isErr())
|
|
233
|
+
return fail(objects.error);
|
|
234
|
+
const object = requiredObject(objects.value, sha);
|
|
235
|
+
if (object.isErr())
|
|
236
|
+
return fail(object.error);
|
|
237
|
+
if (object.value.type !== "blob") {
|
|
238
|
+
return fail(new ObjectDecodeError({
|
|
239
|
+
reason: "unexpected-object-type",
|
|
240
|
+
message: `object is not a blob: ${sha}`,
|
|
241
|
+
objectType: object.value.type,
|
|
242
|
+
sha,
|
|
243
|
+
}));
|
|
244
|
+
}
|
|
245
|
+
return Result.ok(object.value.content);
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Fetch the commit at `ref` *and* its root tree from a single snapshot pack.
|
|
249
|
+
*
|
|
250
|
+
* Forces `parseFull: true` so the tree object — which the commit references
|
|
251
|
+
* but doesn't `want` directly — ends up in the local object store. Useful
|
|
252
|
+
* for "list files at HEAD" without a clone.
|
|
253
|
+
*/
|
|
254
|
+
async fetchTreeForCommit(ref, options = {}) {
|
|
255
|
+
const commit = await this.fetchCommit(ref, { ...options, parseFull: true });
|
|
256
|
+
if (commit.isErr())
|
|
257
|
+
return fail(commit.error);
|
|
258
|
+
const treeObject = this.objects.get(commit.value.commit.tree);
|
|
259
|
+
if (!treeObject) {
|
|
260
|
+
return fail(new ObjectNotFoundError({
|
|
261
|
+
sha: commit.value.commit.tree,
|
|
262
|
+
message: `tree ${commit.value.commit.tree} not present in snapshot pack`,
|
|
263
|
+
}));
|
|
264
|
+
}
|
|
265
|
+
if (treeObject.type !== "tree") {
|
|
266
|
+
return fail(new ObjectDecodeError({
|
|
267
|
+
reason: "unexpected-object-type",
|
|
268
|
+
message: `object is not a tree: ${commit.value.commit.tree}`,
|
|
269
|
+
objectType: treeObject.type,
|
|
270
|
+
sha: commit.value.commit.tree,
|
|
271
|
+
}));
|
|
272
|
+
}
|
|
273
|
+
const entries = parseTree(treeObject.content);
|
|
274
|
+
if (entries.isErr())
|
|
275
|
+
return fail(entries.error);
|
|
276
|
+
return Result.ok({
|
|
277
|
+
commit: commit.value.commit,
|
|
278
|
+
commitSha: commit.value.sha,
|
|
279
|
+
entries: entries.value,
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
/** Fetch and decode a single tree by sha. */
|
|
283
|
+
async fetchTree(sha) {
|
|
284
|
+
const profile = this.profile ? Result.ok(this.profile) : await this.discover();
|
|
285
|
+
if (profile.isErr())
|
|
286
|
+
return fail(profile.error);
|
|
287
|
+
const objects = await this.fetchObjects([sha]);
|
|
288
|
+
if (objects.isErr())
|
|
289
|
+
return fail(objects.error);
|
|
290
|
+
const object = requiredObject(objects.value, sha);
|
|
291
|
+
if (object.isErr())
|
|
292
|
+
return fail(object.error);
|
|
293
|
+
if (object.value.type !== "tree") {
|
|
294
|
+
return fail(new ObjectDecodeError({
|
|
295
|
+
reason: "unexpected-object-type",
|
|
296
|
+
message: `object is not a tree: ${sha}`,
|
|
297
|
+
objectType: object.value.type,
|
|
298
|
+
sha,
|
|
299
|
+
}));
|
|
300
|
+
}
|
|
301
|
+
const tree = parseTree(object.value.content);
|
|
302
|
+
if (tree.isErr())
|
|
303
|
+
return fail(tree.error);
|
|
304
|
+
return Result.ok(tree.value);
|
|
305
|
+
}
|
|
306
|
+
/** Look up an already-materialized object by sha. Does not fetch on miss. */
|
|
307
|
+
getObject(sha) {
|
|
308
|
+
return this.objects.get(sha);
|
|
309
|
+
}
|
|
310
|
+
async fetchObjects(wants, depth, filterSpec, parseFull = false) {
|
|
311
|
+
const normalized = this.normalizeWants(wants, depth, filterSpec);
|
|
312
|
+
if (normalized.length === 0) {
|
|
313
|
+
return Result.ok(this.objects);
|
|
314
|
+
}
|
|
315
|
+
this.logger.debug(`fetchObjects wants=${normalized.length} depth=${depth ?? "-"} filter=${filterSpec ?? "-"}`);
|
|
316
|
+
const pack = await this.fetchPack(normalized, depth, filterSpec);
|
|
317
|
+
if (pack.isErr())
|
|
318
|
+
return fail(pack.error);
|
|
319
|
+
const parseStart = performance.now();
|
|
320
|
+
const targets = !parseFull && normalized.length === 1 ? new Set(normalized) : undefined;
|
|
321
|
+
const parsed = parsePackfile(pack.value, targets);
|
|
322
|
+
const parseMs = performance.now() - parseStart;
|
|
323
|
+
if (parsed.isErr())
|
|
324
|
+
return fail(parsed.error);
|
|
325
|
+
const counts = countByType(parsed.value);
|
|
326
|
+
this.packLogger.recordPack({
|
|
327
|
+
bytes: pack.value.length,
|
|
328
|
+
durationMs: parseMs,
|
|
329
|
+
byType: counts,
|
|
330
|
+
});
|
|
331
|
+
this.packLogger.debug(`parsed ${parsed.value.size} objects (${counts.commit}c/${counts.tree}t/${counts.blob}b/${counts.tag}T) in ${parseMs.toFixed(1)}ms`);
|
|
332
|
+
for (const [sha, object] of parsed.value) {
|
|
333
|
+
this.objects.set(sha, object);
|
|
334
|
+
}
|
|
335
|
+
if (depth === 1 && filterSpec === undefined) {
|
|
336
|
+
for (const sha of normalized)
|
|
337
|
+
this.snapshotCommits.add(sha);
|
|
338
|
+
}
|
|
339
|
+
return Result.ok(this.objects);
|
|
340
|
+
}
|
|
341
|
+
async probeFilter(profile, sha, verbose) {
|
|
342
|
+
const caps = buildCaps({ shallow: true, filter: true });
|
|
343
|
+
const blobNonePack = await this.fetchPack([sha], 1, "blob:none", caps);
|
|
344
|
+
if (blobNonePack.isErr()) {
|
|
345
|
+
if (verbose)
|
|
346
|
+
this.log(`filter blob:none probe failed: ${blobNonePack.error}`);
|
|
347
|
+
}
|
|
348
|
+
else {
|
|
349
|
+
const parsed = parsePackfile(blobNonePack.value);
|
|
350
|
+
if (parsed.isErr()) {
|
|
351
|
+
if (verbose)
|
|
352
|
+
this.log(`filter blob:none probe failed: ${parsed.error}`);
|
|
353
|
+
}
|
|
354
|
+
else {
|
|
355
|
+
const counts = countByType(parsed.value);
|
|
356
|
+
if (verbose)
|
|
357
|
+
this.log(`filter blob:none probe: ${counts.tree} trees, ${counts.blob} blobs`);
|
|
358
|
+
profile.supportsFilterBlobNone = counts.blob === 0 ||
|
|
359
|
+
counts.blob < Math.floor(counts.tree / 4);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
const tree0Pack = await this.fetchPack([sha], 1, "tree:0", caps);
|
|
363
|
+
if (tree0Pack.isErr()) {
|
|
364
|
+
if (verbose)
|
|
365
|
+
this.log(`filter tree:0 probe failed: ${tree0Pack.error}`);
|
|
366
|
+
}
|
|
367
|
+
else {
|
|
368
|
+
const parsed = parsePackfile(tree0Pack.value);
|
|
369
|
+
if (parsed.isErr()) {
|
|
370
|
+
if (verbose)
|
|
371
|
+
this.log(`filter tree:0 probe failed: ${parsed.error}`);
|
|
372
|
+
}
|
|
373
|
+
else {
|
|
374
|
+
const counts = countByType(parsed.value);
|
|
375
|
+
profile.supportsFilterTree0 = counts.tree === 0;
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
async fetchPack(wants, depth, filterSpec, caps) {
|
|
380
|
+
const protocolVersion = this.profile?.protocolVersion ?? 0;
|
|
381
|
+
const requestCaps = caps ?? buildCaps({
|
|
382
|
+
shallow: depth !== undefined,
|
|
383
|
+
filter: filterSpec !== undefined,
|
|
384
|
+
});
|
|
385
|
+
const body = buildFetchRequest({
|
|
386
|
+
wants,
|
|
387
|
+
caps: requestCaps,
|
|
388
|
+
depth,
|
|
389
|
+
filterSpec,
|
|
390
|
+
protocolVersion,
|
|
391
|
+
});
|
|
392
|
+
if (body.isErr())
|
|
393
|
+
return fail(body.error);
|
|
394
|
+
const response = await postUploadPack(this.url, body.value, {
|
|
395
|
+
protocolVersion,
|
|
396
|
+
logger: this.transportLogger,
|
|
397
|
+
});
|
|
398
|
+
if (response.isErr())
|
|
399
|
+
return fail(response.error);
|
|
400
|
+
const pack = extractPack(response.value.body, this.diagnostic);
|
|
401
|
+
if (pack.isErr())
|
|
402
|
+
return fail(pack.error);
|
|
403
|
+
this.packLogger.debug(`extracted pack: ${pack.value.length}B`);
|
|
404
|
+
return Result.ok(pack.value);
|
|
405
|
+
}
|
|
406
|
+
normalizeWants(wants, depth, filterSpec) {
|
|
407
|
+
if (depth === 1 && filterSpec === undefined) {
|
|
408
|
+
return wants.every((want) => this.snapshotCommits.has(want)) ? [] : wants;
|
|
409
|
+
}
|
|
410
|
+
return wants.filter((want) => !this.objects.has(want));
|
|
411
|
+
}
|
|
412
|
+
pickProbeSha(profile) {
|
|
413
|
+
for (const ref of ["HEAD", "refs/heads/main", "refs/heads/master"]) {
|
|
414
|
+
const sha = profile.refs.get(ref);
|
|
415
|
+
if (sha)
|
|
416
|
+
return Result.ok(sha);
|
|
417
|
+
}
|
|
418
|
+
const next = profile.refs.values().next();
|
|
419
|
+
if (!next.done)
|
|
420
|
+
return Result.ok(next.value);
|
|
421
|
+
return fail(new ObjectNotFoundError({ sha: "", message: "no refs available for probe" }));
|
|
422
|
+
}
|
|
423
|
+
log(message) {
|
|
424
|
+
this.diagnostic?.(message);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
function requiredObject(objects, sha) {
|
|
428
|
+
const object = objects.get(sha);
|
|
429
|
+
if (!object) {
|
|
430
|
+
return fail(new ObjectNotFoundError({ sha, message: `object not found: ${sha}` }));
|
|
431
|
+
}
|
|
432
|
+
return Result.ok(object);
|
|
433
|
+
}
|
|
434
|
+
function countByType(objects) {
|
|
435
|
+
const counts = { commit: 0, tree: 0, blob: 0, tag: 0 };
|
|
436
|
+
for (const obj of objects.values())
|
|
437
|
+
counts[obj.type]++;
|
|
438
|
+
return counts;
|
|
439
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|