From dcb0fb74c5fbcbd810d5fea90093b2245bbf2d27 Mon Sep 17 00:00:00 2001 From: zhenyi <434836402@qq.com> Date: Thu, 4 Jun 2026 13:05:38 +0800 Subject: [PATCH] feat(core): implement Git repository operations with gRPC services - Add advertise_refs functionality for Git protocol communication - Implement archive service with TAR/ZIP format support and streaming - Create blame service for Git file annotation with line tracking - Add branch management including create, delete, rename and compare operations - Implement merge checking with conflict detection and fast-forward handling - Add cherry-pick functionality for applying commits between branches - Integrate gix library for Git repository operations and object handling - Add comprehensive test suite covering all Git operations - Implement proper error handling and repository validation - Add pagination support for large result sets - Create protobuf definitions for all Git operations and data structures - Add build system for gRPC code generation and dependency management --- .gitignore | 1 + .idea/.gitignore | 10 + .idea/gitks.iml | 14 + .idea/modules.xml | 8 + .idea/vcs.xml | 6 + Cargo.lock | 2961 ++++++++++ Cargo.toml | 33 + LICENSE | 58 + README.md | 7 + archive/get_archive.rs | 45 + archive/list_archive_entries.rs | 68 + archive/mod.rs | 2 + bare.rs | 112 + blame/do_blame.rs | 152 + blame/mod.rs | 1 + blob/get_blob.rs | 70 + blob/get_raw_blob.rs | 16 + blob/mod.rs | 2 + branch/compare_branch.rs | 54 + branch/create_branch.rs | 36 + branch/delete_branch.rs | 23 + branch/get_branch.rs | 23 + branch/list_branches.rs | 78 + branch/mod.rs | 8 + branch/rename_branch.rs | 25 + branch/set_branch_upstream.rs | 36 + branch/update_branch_target.rs | 39 + build.rs | 51 + commit/cherry_pick_commit.rs | 161 + commit/compare_commits.rs | 94 + commit/create_commit.rs | 375 ++ commit/get_commit.rs | 76 + commit/get_commit_ancestors.rs | 28 + commit/list_commits.rs | 86 + commit/mod.rs | 7 + commit/revert_commit.rs | 146 + commit/types.rs | 1 + diff/get_commit_diff.rs | 86 + diff/get_diff.rs | 330 ++ diff/get_diff_stats.rs | 108 + diff/get_patch.rs | 42 + diff/mod.rs | 4 + error.rs | 78 + init.rs | 24 + lib.rs | 17 + merge/check_merge.rs | 111 + merge/do_merge.rs | 173 + merge/list_merge_conflicts.rs | 69 + merge/mod.rs | 5 + merge/rebase.rs | 192 + merge/resolve_merge_conflicts.rs | 128 + oid.rs | 53 + pack/advertise_refs.rs | 57 + pack/fsck.rs | 45 + pack/index_pack.rs | 138 + pack/list_packfiles.rs | 92 + pack/mod.rs | 7 + pack/pack_objects.rs | 160 + pack/receive_pack.rs | 142 + pack/upload_pack.rs | 147 + paginate.rs | 46 + pb/gitks.rs | 8923 ++++++++++++++++++++++++++++++ pb/mod.rs | 5 + proto/archive.proto | 58 + proto/blame.proto | 56 + proto/branch.proto | 114 + proto/commit.proto | 165 + proto/diff.proto | 140 + proto/merge.proto | 139 + proto/oid.proto | 64 + proto/pack.proto | 134 + proto/repository.proto | 157 + proto/tag.proto | 67 + proto/tagger.proto | 51 + proto/tree.proto | 118 + refs/list_refs.rs | 25 + refs/mod.rs | 1 + tag/create_tag.rs | 46 + tag/delete_tag.rs | 29 + tag/get_tag.rs | 63 + tag/list_tags.rs | 41 + tag/mod.rs | 5 + tag/verify_tag.rs | 35 + tests/archive_test.rs | 169 + tests/blame_test.rs | 132 + tests/branch_test.rs | 200 + tests/commit_test.rs | 469 ++ tests/common/mod.rs | 167 + tests/diff_test.rs | 236 + tests/integration.rs | 743 +++ tests/merge_test.rs | 300 + tests/tag_test.rs | 146 + tests/tree_test.rs | 176 + tree/find_files.rs | 56 + tree/get_file_metadata.rs | 38 + tree/get_tree.rs | 43 + tree/list_tree.rs | 87 + tree/mod.rs | 4 + 98 files changed, 20569 insertions(+) create mode 100644 .gitignore create mode 100644 .idea/.gitignore create mode 100644 .idea/gitks.iml create mode 100644 .idea/modules.xml create mode 100644 .idea/vcs.xml create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 LICENSE create mode 100644 README.md create mode 100644 archive/get_archive.rs create mode 100644 archive/list_archive_entries.rs create mode 100644 archive/mod.rs create mode 100644 bare.rs create mode 100644 blame/do_blame.rs create mode 100644 blame/mod.rs create mode 100644 blob/get_blob.rs create mode 100644 blob/get_raw_blob.rs create mode 100644 blob/mod.rs create mode 100644 branch/compare_branch.rs create mode 100644 branch/create_branch.rs create mode 100644 branch/delete_branch.rs create mode 100644 branch/get_branch.rs create mode 100644 branch/list_branches.rs create mode 100644 branch/mod.rs create mode 100644 branch/rename_branch.rs create mode 100644 branch/set_branch_upstream.rs create mode 100644 branch/update_branch_target.rs create mode 100644 build.rs create mode 100644 commit/cherry_pick_commit.rs create mode 100644 commit/compare_commits.rs create mode 100644 commit/create_commit.rs create mode 100644 commit/get_commit.rs create mode 100644 commit/get_commit_ancestors.rs create mode 100644 commit/list_commits.rs create mode 100644 commit/mod.rs create mode 100644 commit/revert_commit.rs create mode 100644 commit/types.rs create mode 100644 diff/get_commit_diff.rs create mode 100644 diff/get_diff.rs create mode 100644 diff/get_diff_stats.rs create mode 100644 diff/get_patch.rs create mode 100644 diff/mod.rs create mode 100644 error.rs create mode 100644 init.rs create mode 100644 lib.rs create mode 100644 merge/check_merge.rs create mode 100644 merge/do_merge.rs create mode 100644 merge/list_merge_conflicts.rs create mode 100644 merge/mod.rs create mode 100644 merge/rebase.rs create mode 100644 merge/resolve_merge_conflicts.rs create mode 100644 oid.rs create mode 100644 pack/advertise_refs.rs create mode 100644 pack/fsck.rs create mode 100644 pack/index_pack.rs create mode 100644 pack/list_packfiles.rs create mode 100644 pack/mod.rs create mode 100644 pack/pack_objects.rs create mode 100644 pack/receive_pack.rs create mode 100644 pack/upload_pack.rs create mode 100644 paginate.rs create mode 100644 pb/gitks.rs create mode 100644 pb/mod.rs create mode 100644 proto/archive.proto create mode 100644 proto/blame.proto create mode 100644 proto/branch.proto create mode 100644 proto/commit.proto create mode 100644 proto/diff.proto create mode 100644 proto/merge.proto create mode 100644 proto/oid.proto create mode 100644 proto/pack.proto create mode 100644 proto/repository.proto create mode 100644 proto/tag.proto create mode 100644 proto/tagger.proto create mode 100644 proto/tree.proto create mode 100644 refs/list_refs.rs create mode 100644 refs/mod.rs create mode 100644 tag/create_tag.rs create mode 100644 tag/delete_tag.rs create mode 100644 tag/get_tag.rs create mode 100644 tag/list_tags.rs create mode 100644 tag/mod.rs create mode 100644 tag/verify_tag.rs create mode 100644 tests/archive_test.rs create mode 100644 tests/blame_test.rs create mode 100644 tests/branch_test.rs create mode 100644 tests/commit_test.rs create mode 100644 tests/common/mod.rs create mode 100644 tests/diff_test.rs create mode 100644 tests/integration.rs create mode 100644 tests/merge_test.rs create mode 100644 tests/tag_test.rs create mode 100644 tests/tree_test.rs create mode 100644 tree/find_files.rs create mode 100644 tree/get_file_metadata.rs create mode 100644 tree/get_tree.rs create mode 100644 tree/list_tree.rs create mode 100644 tree/mod.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..f6906f2 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,10 @@ +# 默认忽略的文件 +/shelf/ +/workspace.xml +# 基于编辑器的 HTTP 客户端请求 +/httpRequests/ +# 已忽略包含查询文件的默认文件夹 +/queries/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/gitks.iml b/.idea/gitks.iml new file mode 100644 index 0000000..c8ef7c9 --- /dev/null +++ b/.idea/gitks.iml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..e7d02c9 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..daf51ac --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,2961 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + +[[package]] +name = "anyhow" +version = "1.0.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" + +[[package]] +name = "arc-swap" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a3a1fd6f75306b68087b831f025c712524bcb19aad54e557b1129cfa0a2b207" +dependencies = [ + "rustversion", +] + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "async-stream" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476" +dependencies = [ + "async-stream-impl", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-stream-impl" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "async-trait" +version = "0.1.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "autocfg" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" + +[[package]] +name = "axum" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edca88bc138befd0323b20752846e6587272d3b03b0343c8ea28a6f819e6e71f" +dependencies = [ + "async-trait", + "axum-core", + "bytes", + "futures-util", + "http", + "http-body", + "http-body-util", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "rustversion", + "serde", + "sync_wrapper", + "tower 0.5.3", + "tower-layer", + "tower-service", +] + +[[package]] +name = "axum-core" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09f2bd6146b97ae3359fa0cc6d6b376d9539582c7b4220f041a33ec24c226199" +dependencies = [ + "async-trait", + "bytes", + "futures-util", + "http", + "http-body", + "http-body-util", + "mime", + "pin-project-lite", + "rustversion", + "sync_wrapper", + "tower-layer", + "tower-service", +] + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bitflags" +version = "2.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84d7ced0ae9557296835c32bf1b1e02b44c746701f898460fb000d7eaa84f00a" +dependencies = [ + "serde_core", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block-buffer" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdd35008169921d80bc60d3d0ab416eecb028c4cd653352907921d95084790be" +dependencies = [ + "hybrid-array", +] + +[[package]] +name = "bstr" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63044e1ae8e69f3b5a92c736ca6269b8d12fa7efe39bf34ddb06d102cf0e2cab" +dependencies = [ + "memchr", + "regex-automata", + "serde", +] + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "clru" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "197fd99cb113a8d5d9b6376f3aa817f32c1078f2343b714fff7d2ca44fdf67d5" +dependencies = [ + "hashbrown 0.16.1", +] + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "cpufeatures" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "crypto-common" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" +dependencies = [ + "hybrid-array", +] + +[[package]] +name = "dashmap" +version = "6.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6361d5c062261c78a176addb82d4c821ae42bed6089de0e12603cd25de2059c" +dependencies = [ + "cfg-if", + "crossbeam-utils", + "hashbrown 0.14.5", + "lock_api", + "once_cell", + "parking_lot_core", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer 0.10.4", + "crypto-common 0.1.7", +] + +[[package]] +name = "digest" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" +dependencies = [ + "block-buffer 0.12.0", + "crypto-common 0.2.2", +] + +[[package]] +name = "document-features" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61" +dependencies = [ + "litrs", +] + +[[package]] +name = "duct" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e66e9c0c03d094e1a0ba1be130b849034aa80c3a2ab8ee94316bc809f3fa684" +dependencies = [ + "libc", + "os_pipe", + "shared_child", + "shared_thread", +] + +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + +[[package]] +name = "either" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" + +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "faster-hex" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7223ae2d2f179b803433d9c830478527e92b8117eab39460edae7f1614d9fb73" +dependencies = [ + "heapless", + "serde", +] + +[[package]] +name = "fastrand" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" + +[[package]] +name = "filetime" +version = "0.2.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c287a33c7f0a620c38e641e7f60827713987b3c0f26e8ddc9462cc69cf75759" +dependencies = [ + "cfg-if", + "libc", +] + +[[package]] +name = "fixedbitset" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" + +[[package]] +name = "flate2" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" +dependencies = [ + "miniz_oxide", + "zlib-rs", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + +[[package]] +name = "futures-channel" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" + +[[package]] +name = "futures-sink" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" + +[[package]] +name = "futures-task" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" + +[[package]] +name = "futures-util" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "getrandom" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasip2", + "wasip3", +] + +[[package]] +name = "gitks" +version = "1.0.0" +dependencies = [ + "duct", + "gix", + "gix-archive", + "prost", + "prost-types", + "serde", + "tempfile", + "thiserror", + "tokio", + "tokio-stream", + "tonic", + "tonic-build", + "tracing", +] + +[[package]] +name = "gix" +version = "0.84.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae54ae0ebd1a5a3c3f8d95dd3b5ca6e63f4fed9bfd585e13801a97d7bde8f9ce" +dependencies = [ + "gix-actor", + "gix-attributes", + "gix-blame", + "gix-command", + "gix-commitgraph", + "gix-config", + "gix-credentials", + "gix-date", + "gix-diff", + "gix-discover", + "gix-error", + "gix-features", + "gix-filter", + "gix-fs", + "gix-glob", + "gix-hash", + "gix-hashtable", + "gix-ignore", + "gix-index", + "gix-lock", + "gix-mailmap", + "gix-merge", + "gix-object", + "gix-odb", + "gix-pack", + "gix-path", + "gix-pathspec", + "gix-protocol", + "gix-ref", + "gix-refspec", + "gix-revision", + "gix-revwalk", + "gix-sec", + "gix-shallow", + "gix-submodule", + "gix-tempfile", + "gix-trace", + "gix-transport", + "gix-traverse", + "gix-url", + "gix-utils", + "gix-validate", + "gix-worktree", + "gix-worktree-stream", + "nonempty", + "serde", + "smallvec", + "thiserror", +] + +[[package]] +name = "gix-actor" +version = "0.41.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bc998b8f746dda8565450d08a63b792ced9165d8c27a1ed3f02799ec6a7820f" +dependencies = [ + "bstr", + "gix-date", + "gix-error", + "serde", +] + +[[package]] +name = "gix-archive" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16909cacc78936ab96f6c3be08379d0a2e88bfa3a7527972d2ed75c7517ef31e" +dependencies = [ + "bstr", + "document-features", + "flate2", + "gix-date", + "gix-error", + "gix-object", + "gix-path", + "gix-worktree-stream", + "rawzip", + "tar", +] + +[[package]] +name = "gix-attributes" +version = "0.33.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d43f12e246d3bf7ec624c8fc15ac4a4b62b7c4c6f586cb82be6c90bf84c9d02" +dependencies = [ + "bstr", + "gix-glob", + "gix-path", + "gix-quote", + "gix-trace", + "kstring", + "serde", + "smallvec", + "thiserror", + "unicode-bom", +] + +[[package]] +name = "gix-bitmap" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ebef0c26ad305747649e727bbcd56a7b7910754eb7cea88f6dff6f93c51283" +dependencies = [ + "gix-error", +] + +[[package]] +name = "gix-blame" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d39a0c14af94c2edaa5eefe06d5ef2cdea55316ae9a9321314288e3f55fa4c0" +dependencies = [ + "gix-commitgraph", + "gix-date", + "gix-diff", + "gix-error", + "gix-hash", + "gix-object", + "gix-revwalk", + "gix-trace", + "gix-traverse", + "gix-worktree", + "smallvec", + "thiserror", +] + +[[package]] +name = "gix-chunk" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9faee47943b638e58ddd5e275a4906ad3e4b6c8584f1d41bd18ab9032ec52afb" +dependencies = [ + "gix-error", +] + +[[package]] +name = "gix-command" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00706d4fef135ef4b01680d5218c6ee40cda8baf697b864296cbc887d19118f6" +dependencies = [ + "bstr", + "gix-path", + "gix-quote", + "gix-trace", + "shell-words", +] + +[[package]] +name = "gix-commitgraph" +version = "0.37.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f675d0df484a7f6a47e64bd6f311af489d947c0323b0564f36d14f3d7762abb" +dependencies = [ + "bstr", + "gix-chunk", + "gix-error", + "gix-hash", + "memmap2", + "nonempty", + "serde", +] + +[[package]] +name = "gix-config" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f2372d4b49ca28431e7d150cab9d25edc1890f0184bd57eb0e917c7799e63de" +dependencies = [ + "bstr", + "gix-config-value", + "gix-features", + "gix-glob", + "gix-path", + "gix-ref", + "gix-sec", + "smallvec", + "thiserror", + "unicode-bom", +] + +[[package]] +name = "gix-config-value" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed42168329552f6c2e5df09665c104199d45d84bedb53683738a49b57fe1baab" +dependencies = [ + "bitflags", + "bstr", + "gix-path", + "libc", + "thiserror", +] + +[[package]] +name = "gix-credentials" +version = "0.38.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40cd22f0dd71988be12d6e78b1709de2370e1957c5f107ff31e56caeba3745d" +dependencies = [ + "bstr", + "gix-command", + "gix-config-value", + "gix-date", + "gix-path", + "gix-prompt", + "gix-sec", + "gix-trace", + "gix-url", + "serde", + "thiserror", +] + +[[package]] +name = "gix-date" +version = "0.15.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3ecab64a98bbac9f8e02990a9ea5e3c974a7d49b95f2bd70ad94ad22fa6b48c" +dependencies = [ + "bstr", + "gix-error", + "itoa", + "jiff", + "serde", +] + +[[package]] +name = "gix-diff" +version = "0.64.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b6d9528f32d94cef2edf39a1ac01fe5a0fc44ddbb18d9e44099936047c3302b" +dependencies = [ + "bstr", + "gix-command", + "gix-filter", + "gix-fs", + "gix-hash", + "gix-imara-diff", + "gix-object", + "gix-path", + "gix-tempfile", + "gix-trace", + "gix-traverse", + "gix-worktree", + "thiserror", +] + +[[package]] +name = "gix-discover" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77bacdd12b7879d2178a80c58c2f319995e4654e1a7a23e3181e5c8a12b824f7" +dependencies = [ + "bstr", + "dunce", + "gix-fs", + "gix-path", + "gix-ref", + "gix-sec", + "thiserror", +] + +[[package]] +name = "gix-error" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e57831e199be480af90dcd7e459abed8a174c09ec9a6e2cc8f7ca6c54598b06b" +dependencies = [ + "bstr", +] + +[[package]] +name = "gix-features" +version = "0.48.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1849ae154d38bc403185be14fa871e38e3c93ee606875d94e207fdb9fba52dbc" +dependencies = [ + "bytes", + "crc32fast", + "crossbeam-channel", + "gix-path", + "gix-trace", + "gix-utils", + "libc", + "once_cell", + "parking_lot", + "prodash", + "thiserror", + "walkdir", + "zlib-rs", +] + +[[package]] +name = "gix-filter" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecf74b7d16f6694ce4a3049074c41be0c7987105743674f1671807bd6dce09fa" +dependencies = [ + "bstr", + "encoding_rs", + "gix-attributes", + "gix-command", + "gix-hash", + "gix-object", + "gix-packetline", + "gix-path", + "gix-quote", + "gix-trace", + "gix-utils", + "smallvec", + "thiserror", +] + +[[package]] +name = "gix-fs" +version = "0.21.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6cdff46db8798e47e2f727d84b9379aac5add3dd3d9d0b07bb4d7d5d640771fe" +dependencies = [ + "bstr", + "fastrand", + "gix-features", + "gix-path", + "gix-utils", + "thiserror", +] + +[[package]] +name = "gix-glob" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1fcb8ef5b16bcf874abe9b68d8abb3c0493c876d367ab824151f30a0f3f3756" +dependencies = [ + "bitflags", + "bstr", + "gix-features", + "gix-path", + "serde", +] + +[[package]] +name = "gix-hash" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb0926d3819c837750b4e03c7754901e73f68b8c9b690753a6372a1bed4eedce" +dependencies = [ + "faster-hex", + "gix-features", + "serde", + "sha1-checked", + "sha2", + "thiserror", +] + +[[package]] +name = "gix-hashtable" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0e30b93eea8718baf7d8153fcb938e2926175bbf18097c09f1c01b6f0be0563" +dependencies = [ + "gix-hash", + "hashbrown 0.17.1", + "parking_lot", +] + +[[package]] +name = "gix-ignore" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d491bab9bf2c9f341dc754f425c31d5d3f63aca615312167b82e1deeaca97d8d" +dependencies = [ + "bstr", + "gix-glob", + "gix-path", + "gix-trace", + "serde", + "unicode-bom", +] + +[[package]] +name = "gix-imara-diff" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19753d40da53d0ec41604750eeb969097a90fb2d7f7992730d904541c04e2c19" +dependencies = [ + "bstr", + "hashbrown 0.17.1", +] + +[[package]] +name = "gix-index" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e6b28cc592dc753adb58302bb14a64e412ee591a3bec77aa4df87bff74fa80d" +dependencies = [ + "bitflags", + "bstr", + "filetime", + "fnv", + "gix-bitmap", + "gix-features", + "gix-fs", + "gix-hash", + "gix-lock", + "gix-object", + "gix-traverse", + "gix-utils", + "gix-validate", + "hashbrown 0.17.1", + "itoa", + "libc", + "memmap2", + "rustix", + "serde", + "smallvec", + "thiserror", +] + +[[package]] +name = "gix-lock" +version = "23.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65c9dedd9e90b0d47624d2ed241d394e09294118364e87b9b7e5f1fe755f3c2c" +dependencies = [ + "gix-tempfile", + "gix-utils", + "thiserror", +] + +[[package]] +name = "gix-mailmap" +version = "0.33.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "195fd20808055824531be2fd0d34136d900e5fbca3ffb0a3c07e8beeefb9c828" +dependencies = [ + "bstr", + "gix-actor", + "gix-date", + "gix-error", + "serde", +] + +[[package]] +name = "gix-merge" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7543e1eceb25fbbd1a29459c794148d8bafc1c77555eb386617a2d99b5371971" +dependencies = [ + "bstr", + "gix-command", + "gix-diff", + "gix-filter", + "gix-fs", + "gix-hash", + "gix-imara-diff", + "gix-index", + "gix-object", + "gix-path", + "gix-quote", + "gix-revision", + "gix-revwalk", + "gix-tempfile", + "gix-trace", + "gix-worktree", + "nonempty", + "thiserror", +] + +[[package]] +name = "gix-object" +version = "0.61.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5cd857e29429c7213bdef3f5aef83f8cc124774fe8ae0d27b1607d218d6d525" +dependencies = [ + "bstr", + "gix-actor", + "gix-date", + "gix-features", + "gix-hash", + "gix-hashtable", + "gix-utils", + "gix-validate", + "itoa", + "serde", + "smallvec", + "thiserror", +] + +[[package]] +name = "gix-odb" +version = "0.81.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d004c32858b1556f2d7874405edb3c97dc78fc09beaa87d57bb077ee2858a7d" +dependencies = [ + "arc-swap", + "gix-features", + "gix-fs", + "gix-hash", + "gix-hashtable", + "gix-object", + "gix-pack", + "gix-path", + "gix-quote", + "memmap2", + "parking_lot", + "serde", + "tempfile", + "thiserror", +] + +[[package]] +name = "gix-pack" +version = "0.71.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e43626f2a27d1033674ec1a196b845614231e6bbd949d5e21c133045ff56b174" +dependencies = [ + "clru", + "gix-chunk", + "gix-error", + "gix-features", + "gix-hash", + "gix-hashtable", + "gix-object", + "gix-path", + "memmap2", + "serde", + "smallvec", + "thiserror", + "uluru", +] + +[[package]] +name = "gix-packetline" +version = "0.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb18337ba2830bb43367d1af43819c8c78f31337f079fc76d0f1f1750a173126" +dependencies = [ + "bstr", + "faster-hex", + "gix-trace", + "thiserror", +] + +[[package]] +name = "gix-path" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afa6ac14cd14939ea94a496ce7460daa6511c09f5b84757e9cfc6f9c8d0f93a6" +dependencies = [ + "bstr", + "gix-trace", + "gix-validate", + "thiserror", +] + +[[package]] +name = "gix-pathspec" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3050783b41ee11511e1e8fb35623df81806194f4030395f14f48ea37c2798c9f" +dependencies = [ + "bitflags", + "bstr", + "gix-attributes", + "gix-config-value", + "gix-glob", + "gix-path", + "thiserror", +] + +[[package]] +name = "gix-prompt" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ee604d7746080ae7e1023bf47204bcc2c5f307bfbe2306a3c90b1bfd1a2c6d8" +dependencies = [ + "gix-command", + "gix-config-value", + "parking_lot", + "rustix", + "thiserror", +] + +[[package]] +name = "gix-protocol" +version = "0.62.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51dea3acb390707ab868f1f9584f18449eb95d869deffae96768e47d303595ee" +dependencies = [ + "bstr", + "gix-date", + "gix-features", + "gix-hash", + "gix-ref", + "gix-shallow", + "gix-transport", + "gix-utils", + "maybe-async", + "nonempty", + "serde", + "thiserror", +] + +[[package]] +name = "gix-quote" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6e541fc33cc2b783b7979040d445a0c86a2eca747c8faea4ca84230d06ae6ef" +dependencies = [ + "bstr", + "gix-error", + "gix-utils", +] + +[[package]] +name = "gix-ref" +version = "0.64.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c04f64c37eb7e6feb73c7060f8dc6f381cc5de5d53249bfd450bc48a86b2e8b" +dependencies = [ + "gix-actor", + "gix-features", + "gix-fs", + "gix-hash", + "gix-lock", + "gix-object", + "gix-path", + "gix-tempfile", + "gix-utils", + "gix-validate", + "memmap2", + "serde", + "thiserror", +] + +[[package]] +name = "gix-refspec" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b216ae06ec74b5f24ad0142026a997fb0a935b7410eaf9c1616fc3f0e6c5a6d3" +dependencies = [ + "bstr", + "gix-error", + "gix-glob", + "gix-hash", + "gix-revision", + "gix-validate", + "smallvec", + "thiserror", +] + +[[package]] +name = "gix-revision" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b47c88884dd3c1a19a39da19d10211fcdea2809aadc86869b6e824a1774340f" +dependencies = [ + "bitflags", + "bstr", + "gix-commitgraph", + "gix-date", + "gix-error", + "gix-hash", + "gix-hashtable", + "gix-object", + "gix-revwalk", + "gix-trace", + "nonempty", + "serde", +] + +[[package]] +name = "gix-revwalk" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85f5756abffe0917827aac683b13684ed99875bc398fa1f9b8f479b0681ef9e6" +dependencies = [ + "gix-commitgraph", + "gix-date", + "gix-error", + "gix-hash", + "gix-hashtable", + "gix-object", + "smallvec", + "thiserror", +] + +[[package]] +name = "gix-sec" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab8519976e4c7e486270740a5400369f37940779b80bd1377d94cfa1125d01b3" +dependencies = [ + "bitflags", + "gix-path", + "libc", + "serde", + "windows-sys 0.61.2", +] + +[[package]] +name = "gix-shallow" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a292fc2fe548c5dfa575479d16b445b0ddf1dd2f56f1fec6aed386f82553cd97" +dependencies = [ + "bstr", + "gix-hash", + "gix-lock", + "nonempty", + "serde", + "thiserror", +] + +[[package]] +name = "gix-submodule" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3059890ef054066c22a94bfc6a3eaba0d806aedcd630a0bc9e5783fd88884781" +dependencies = [ + "bstr", + "gix-config", + "gix-path", + "gix-pathspec", + "gix-refspec", + "gix-url", + "thiserror", +] + +[[package]] +name = "gix-tempfile" +version = "23.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27850097e1ff9515f46a0dad0f5f9c9d020e972727772dabab9450690c4adb22" +dependencies = [ + "dashmap", + "gix-fs", + "libc", + "parking_lot", + "tempfile", +] + +[[package]] +name = "gix-trace" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44dc45eae785c0eb14173e0f152e6e224dcf4d45b6a6999a3aed22af541ad678" +dependencies = [ + "tracing", +] + +[[package]] +name = "gix-transport" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd0e34995b1aab0fa8dff2af8db726a0bfad3e119c89302604463264046e7ff" +dependencies = [ + "bstr", + "gix-command", + "gix-features", + "gix-packetline", + "gix-quote", + "gix-sec", + "gix-url", + "serde", + "thiserror", +] + +[[package]] +name = "gix-traverse" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8de590ecc86a3b2870665f2288324fa9f7f8672c7fc2d4e020fdd81cd1f7aed" +dependencies = [ + "bitflags", + "gix-commitgraph", + "gix-date", + "gix-hash", + "gix-hashtable", + "gix-object", + "gix-revwalk", + "smallvec", + "thiserror", +] + +[[package]] +name = "gix-url" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bb01ec69d55e82ccb7a19e264501ead4e6aac38463a8cebfdd81e22bb67ab2" +dependencies = [ + "bstr", + "gix-path", + "percent-encoding", + "serde", + "thiserror", +] + +[[package]] +name = "gix-utils" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66c50966184123caf580ffa64e28031a878597f1c7fceb8fe19566c38eb1b771" +dependencies = [ + "fastrand", + "unicode-normalization", +] + +[[package]] +name = "gix-validate" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bc6fc771c4063ba7cd2f47b91fb6076251c6a823b64b7fe7b8874b0fe4afae3" +dependencies = [ + "bstr", +] + +[[package]] +name = "gix-worktree" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cef414ed275e8407cd5d53d301e83be19700b0dd3f859d2434417b58f454a2d1" +dependencies = [ + "bstr", + "gix-attributes", + "gix-fs", + "gix-glob", + "gix-hash", + "gix-ignore", + "gix-index", + "gix-object", + "gix-path", + "gix-validate", + "serde", +] + +[[package]] +name = "gix-worktree-stream" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d25e9ed30100c63f7590bc581c225e53f731a53e06aa79a245739c07f7dcc557" +dependencies = [ + "gix-attributes", + "gix-error", + "gix-features", + "gix-filter", + "gix-fs", + "gix-hash", + "gix-object", + "gix-path", + "gix-traverse", + "parking_lot", +] + +[[package]] +name = "h2" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "171fefbc92fe4a4de27e0698d6a5b392d6a0e333506bc49133760b3bcf948733" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap 2.14.0", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hash32" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606" +dependencies = [ + "byteorder", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash 0.1.5", +] + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash 0.2.0", +] + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash 0.2.0", +] + +[[package]] +name = "heapless" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad" +dependencies = [ + "hash32", + "stable_deref_trait", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "http" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8be7462df143984c4598a256ef469b251d7d7f9e271135073e78fc535414f3d0" +dependencies = [ + "bytes", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "hybrid-array" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9155a582abd142abc056962c29e3ce5ff2ad5469f4246b537ed42c5deba857da" +dependencies = [ + "typenum", +] + +[[package]] +name = "hyper" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55281c53a1894c864990125767da440a4e630446785086f52523b20033b74498" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-timeout" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b90d566bffbce6a75bd8b09a05aa8c2cb1fabb6cb348f8840c9e4c90a0d83b0" +dependencies = [ + "hyper", + "hyper-util", + "pin-project-lite", + "tokio", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "hyper", + "libc", + "pin-project-lite", + "socket2 0.6.4", + "tokio", + "tower-service", + "tracing", +] + +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", +] + +[[package]] +name = "indexmap" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +dependencies = [ + "equivalent", + "hashbrown 0.17.1", + "serde", + "serde_core", +] + +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "jiff" +version = "0.2.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4603d3033e49e2b0e31229fcab20a5d40089c607d975cd9c80551dc69eed9102" +dependencies = [ + "jiff-static", + "jiff-tzdb-platform", + "log", + "portable-atomic", + "portable-atomic-util", + "serde_core", + "windows-link", +] + +[[package]] +name = "jiff-static" +version = "0.2.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "782d32378dddf207193ac91cefb848ad41abb58195c95168e1291227a0832b47" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "jiff-tzdb" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c900ef84826f1338a557697dc8fc601df9ca9af4ac137c7fb61d4c6f2dfd3076" + +[[package]] +name = "jiff-tzdb-platform" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "875a5a69ac2bab1a891711cf5eccbec1ce0341ea805560dcd90b7a2e925132e8" +dependencies = [ + "jiff-tzdb", +] + +[[package]] +name = "kstring" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "558bf9508a558512042d3095138b1f7b8fe90c5467d94f9f1da28b3731c5dbd1" +dependencies = [ + "serde", + "static_assertions", +] + +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + +[[package]] +name = "libc" +version = "0.2.186" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" + +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + +[[package]] +name = "litrs" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "113b30b4cd05f7c06868fdb2854f66a7b9fece9a48425351cd532e810d74024f" + +[[package]] +name = "matchit" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" + +[[package]] +name = "maybe-async" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "746873a384ad60adc5db74471dfaba74bd278afbdcfd81db93fafcdfc8b5ca0c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "memchr" +version = "2.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b947ae49db0d222b1dbc6b113ce7248a3fc3a6ca21b696717bfc000ba4484d8" + +[[package]] +name = "memmap2" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "714098028fe011992e1c3962653c96b2d578c4b4bce9036e15ff220319b1e0e3" +dependencies = [ + "libc", +] + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + +[[package]] +name = "mio" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02bd0af71c67b473010cbbc60715ee815645a4dc942899111f494b4b737d6fda" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.61.2", +] + +[[package]] +name = "multimap" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d87ecb2933e8aeadb3e3a02b828fed80a7528047e68b4f424523a0981a3a084" + +[[package]] +name = "nonempty" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9737e026353e5cd0736f98eddae28665118eb6f6600902a7f50db585621fecb6" +dependencies = [ + "serde", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "os_pipe" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d8fae84b431384b68627d0f9b3b1245fcf9f46f6c0e3dc902e9dce64edd1967" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link", +] + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "petgraph" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772" +dependencies = [ + "fixedbitset", + "indexmap 2.14.0", +] + +[[package]] +name = "pin-project" +version = "1.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2466b2336ed02bcdca6b294417127b90ec92038d1d5c4fbeac971a922e0e0924" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c96395f0a926bc13b1c17622aaddda1ecb55d49c8f1bf9777e4d877800a43f8b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "portable-atomic" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" + +[[package]] +name = "portable-atomic-util" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a106d1259c23fac8e543272398ae0e3c0b8d33c88ed73d0cc71b0f1d902618" +dependencies = [ + "portable-atomic", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "prodash" +version = "31.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "962200e2d7d551451297d9fdce85138374019ada198e30ea9ede38034e27604c" +dependencies = [ + "parking_lot", +] + +[[package]] +name = "prost" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2796faa41db3ec313a31f7624d9286acf277b52de526150b7e69f3debf891ee5" +dependencies = [ + "bytes", + "prost-derive", +] + +[[package]] +name = "prost-build" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be769465445e8c1474e9c5dac2018218498557af32d9ed057325ec9a41ae81bf" +dependencies = [ + "heck", + "itertools", + "log", + "multimap", + "once_cell", + "petgraph", + "prettyplease", + "prost", + "prost-types", + "regex", + "syn", + "tempfile", +] + +[[package]] +name = "prost-derive" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a56d757972c98b346a9b766e3f02746cde6dd1cd1d1d563472929fdd74bec4d" +dependencies = [ + "anyhow", + "itertools", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "prost-types" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52c2c1bf36ddb1a1c396b3601a3cec27c2462e45f07c386894ec3ccf5332bd16" +dependencies = [ + "prost", +] + +[[package]] +name = "quote" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[package]] +name = "rand" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.17", +] + +[[package]] +name = "rawzip" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d9575f44c8cf85bc843ad666dcdf20d05a7753772bef56eb2a5140282b32150" + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex" +version = "1.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" + +[[package]] +name = "rustix" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.150" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "digest 0.10.7", +] + +[[package]] +name = "sha1-checked" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89f599ac0c323ebb1c6082821a54962b839832b03984598375bff3975b804423" +dependencies = [ + "digest 0.10.7", + "sha1", +] + +[[package]] +name = "sha2" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", + "digest 0.11.3", +] + +[[package]] +name = "shared_child" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e362d9935bc50f019969e2f9ecd66786612daae13e8f277be7bfb66e8bed3f7" +dependencies = [ + "libc", + "sigchld", + "windows-sys 0.60.2", +] + +[[package]] +name = "shared_thread" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52b86057fcb5423f5018e331ac04623e32d6b5ce85e33300f92c79a1973928b0" + +[[package]] +name = "shell-words" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc6fe69c597f9c37bfeeeeeb33da3530379845f10be461a66d16d03eca2ded77" + +[[package]] +name = "sigchld" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47106eded3c154e70176fc83df9737335c94ce22f821c32d17ed1db1f83badb1" +dependencies = [ + "libc", + "os_pipe", + "signal-hook", +] + +[[package]] +name = "signal-hook" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +dependencies = [ + "errno", + "libc", +] + +[[package]] +name = "simd-adler32" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +dependencies = [ + "serde", +] + +[[package]] +name = "socket2" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "socket2" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52d1cfed4120b4d927bf7c0f86d2087a4a7d6027c906d9f9d525a80573b9be51" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "syn" +version = "2.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" + +[[package]] +name = "tar" +version = "0.4.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f6221d9a6003c78398e3b239969f352578258df48c8eb051caadae0015bc840" +dependencies = [ + "filetime", + "libc", + "xattr", +] + +[[package]] +name = "tempfile" +version = "3.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" +dependencies = [ + "fastrand", + "getrandom 0.4.2", + "once_cell", + "rustix", + "windows-sys 0.61.2", +] + +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tinyvec" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.52.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe" +dependencies = [ + "bytes", + "libc", + "mio", + "pin-project-lite", + "signal-hook-registry", + "socket2 0.6.4", + "tokio-macros", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-macros" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-stream" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", + "tokio-util", +] + +[[package]] +name = "tokio-util" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tonic" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877c5b330756d856ffcc4553ab34a5684481ade925ecc54bcd1bf02b1d0d4d52" +dependencies = [ + "async-stream", + "async-trait", + "axum", + "base64", + "bytes", + "h2", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-timeout", + "hyper-util", + "percent-encoding", + "pin-project", + "prost", + "socket2 0.5.10", + "tokio", + "tokio-stream", + "tower 0.4.13", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tonic-build" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9557ce109ea773b399c9b9e5dca39294110b74f1f342cb347a80d1fce8c26a11" +dependencies = [ + "prettyplease", + "proc-macro2", + "prost-build", + "prost-types", + "quote", + "syn", +] + +[[package]] +name = "tower" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +dependencies = [ + "futures-core", + "futures-util", + "indexmap 1.9.3", + "pin-project", + "pin-project-lite", + "rand", + "slab", + "tokio", + "tokio-util", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "typenum" +version = "1.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" + +[[package]] +name = "uluru" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c8a2469e56e6e5095c82ccd3afb98dad95f7af7929aab6d8ba8d6e0f73657da" +dependencies = [ + "arrayvec", +] + +[[package]] +name = "unicode-bom" +version = "2.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7eec5d1121208364f6793f7d2e222bf75a915c19557537745b195b253dd64217" + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-normalization" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fd4f6878c9cb28d874b009da9e8d183b5abc80117c40bbd187a1fde336be6e8" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasip2" +version = "1.0.3+wasi-0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" +dependencies = [ + "wit-bindgen 0.57.1", +] + +[[package]] +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" +dependencies = [ + "wit-bindgen 0.51.0", +] + +[[package]] +name = "wasm-encoder" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap 2.14.0", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags", + "hashbrown 0.15.5", + "indexmap 2.14.0", + "semver", +] + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.5", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.53.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +dependencies = [ + "windows-link", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + +[[package]] +name = "wit-bindgen" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck", + "indexmap 2.14.0", + "prettyplease", + "syn", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags", + "indexmap 2.14.0", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap 2.14.0", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] + +[[package]] +name = "xattr" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32e45ad4206f6d2479085147f02bc2ef834ac85886624a23575ae137c8aa8156" +dependencies = [ + "libc", + "rustix", +] + +[[package]] +name = "zerocopy" +version = "0.8.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b065d4f0e55f82fae73202e189638116a87c55ab6b8e6c2721e13dd9d854ad1" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b631b19d36a892ab55420c92dbc83ccd79274f25be714855d3074aa71cab639" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zlib-rs" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3be3d40e40a133f9c916ee3f9f4fa2d9d63435b5fbe1bfc6d9dae0aa0ada1513" + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..b433ef4 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,33 @@ +[package] +name = "gitks" +version = "1.0.0" +edition = "2024" +authors = ["gitks contributors"] +description = "A gRPC-accessible Git repository operations library for bare repositories" +repository = "" +readme = "" +homepage = "" +license = "PolyForm-Noncommercial-1.0.0" +keywords = ["git", "grpc", "bare-repository", "gix"] +categories = ["development-tools"] +documentation = "" + +[lib] +path = "lib.rs" +name = "gitks" +[dependencies] +serde = { version = "1.0.228", features = ["derive"] } +gix = { version = "0.84.0", default-features = false, features = ["serde", "blame", "sha256", "sha1", "tracing", "merge", "max-performance-safe", "revision"] } +gix-archive = { version = "0.33.0", features = ["sha256","sha1","document-features"] } +duct = { version = "1.1.1", features = [] } +tracing = { version = "0.1.32", features = ["log"] } +tokio = { version = "1", features = ["rt-multi-thread", "macros", "process", "io-util", "sync"] } +tokio-stream = { version = "0.1.18", features = ["full"] } +thiserror = { version = "2.0.18", features = [] } +prost = "0.13" +prost-types = "0.13" +tonic = { version = "0.12", features = ["transport"] } +tempfile = "3" + +[build-dependencies] +tonic-build = "0.12" diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..c7728b1 --- /dev/null +++ b/LICENSE @@ -0,0 +1,58 @@ +PolyForm Noncommercial License 1.0.0 + +Copyright (c) 2024 gitks contributors + +License: "Noncommercial" as defined below. + +"Noncommercial" means primarily intended for or directed towards the +advantage or monetary gain of a business, commercial entity, or for-profit +organization. A use is "Noncommercial" if it is not primarily intended for +or directed towards commercial advantage or monetary compensation. + +1. Grant of Copyright License. Subject to the terms of this license, + Licensor grants you a worldwide, royalty-free, non-exclusive, limited + license to exercise the Licensed Rights in the Licensed Material for + Noncommercial purposes only. + +2. Grant of Patent License. Subject to the terms of this license, Licensor + grants you a worldwide, royalty-free, non-exclusive, limited license + under patent claims owned or controlled by Licensor that are embodied + in the Licensed Material as furnished by Licensor, to make, use, sell, + offer for sale, have made, and import the Licensed Material for + Noncommercial purposes only. + +3. Limitations. The license granted in Section 1 and Section 2 above is + expressly limited to Noncommercial purposes. You may not exercise the + Licensed Rights for the purpose of providing services to third parties, + including but not limited to: + (a) offering the Licensed Material as a hosted or managed service + where third parties access or use the Licensed Material; + (b) offering the Licensed Material as part of a product or service + that is sold, licensed, or otherwise provided for monetary gain; + (c) using the Licensed Material to provide consulting, support, or + other services for monetary gain. + +4. Acceptance. Any use of the Licensed Material in violation of this + license will automatically terminate your rights under this license + for the current and all future versions of the Licensed Material. + +5. Patents. If you institute patent litigation against any entity + (including a cross-claim or counterclaim in a lawsuit) alleging that + the Licensed Material constitutes direct or contributory patent + infringement, then any patent licenses granted to you under this + license for the Licensed Material shall terminate as of the date + such litigation is filed. + +6. Disclaimer of Warranty. THE LICENSED MATERIAL IS PROVIDED "AS IS" AND + WITHOUT ANY WARRANTY OF ANY KIND. LICENSOR DISCLAIMS ALL WARRANTIES, + EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED WARRANTIES + OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A + PARTICULAR PURPOSE. + +7. Limitation of Liability. IN NO EVENT WILL LICENSOR BE LIABLE TO YOU + FOR ANY DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL, OR + CONSEQUENTIAL DAMAGES ARISING OUT OF THESE TERMS OR IN CONNECTION + WITH THE USE OR INABILITY TO USE THE LICENSED MATERIAL, EVEN IF + LICENSOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + +For the full license text, see: https://polyformproject.org/licenses/noncommercial/1.0.0 diff --git a/README.md b/README.md new file mode 100644 index 0000000..7cab0a9 --- /dev/null +++ b/README.md @@ -0,0 +1,7 @@ +# gitks +A Git bare repository operation library based on gRPC. + + +## License + +[PolyForm Noncommercial 1.0.0](LICENSE) — Free for noncommercial use. For commercial licenses, please contact us. \ No newline at end of file diff --git a/archive/get_archive.rs b/archive/get_archive.rs new file mode 100644 index 0000000..14e2134 --- /dev/null +++ b/archive/get_archive.rs @@ -0,0 +1,45 @@ +use std::process::Command; + +use crate::bare::GitBare; +use crate::error::{GitError, GitResult}; +use crate::pb::{ArchiveChunk, ArchiveRequest, archive_options, object_selector}; + +impl GitBare { + pub fn get_archive(&self, request: ArchiveRequest) -> GitResult> { + let revision = match request.treeish.and_then(|s| s.selector) { + Some(object_selector::Selector::Oid(oid)) => oid.hex, + Some(object_selector::Selector::Revision(name)) => name.revision, + None => "HEAD".into(), + }; + let options = request.options.unwrap_or_default(); + let format = archive_options::Format::try_from(options.format) + .unwrap_or(archive_options::Format::ArchiveFormatTar); + let mut args = vec!["archive".to_string()]; + args.push(match format { + archive_options::Format::ArchiveFormatZip => "--format=zip".into(), + _ => "--format=tar".into(), + }); + if !options.prefix.is_empty() { + args.push(format!("--prefix={}", options.prefix)); + } + args.push(revision); + if !options.pathspec.is_empty() { + args.push("--".into()); + args.extend(options.pathspec); + } + let output = Command::new("git") + .arg("--git-dir") + .arg(&self.bare_dir) + .args(&args) + .output()?; + if !output.status.success() { + return Err(GitError::CommandFailed { + status_code: output.status.code(), + stderr: String::from_utf8_lossy(&output.stderr).into_owned(), + }); + } + Ok(vec![ArchiveChunk { + data: output.stdout, + }]) + } +} diff --git a/archive/list_archive_entries.rs b/archive/list_archive_entries.rs new file mode 100644 index 0000000..898fae1 --- /dev/null +++ b/archive/list_archive_entries.rs @@ -0,0 +1,68 @@ +use std::process::Command; + +use crate::bare::GitBare; +use crate::error::{GitError, GitResult}; +use crate::pb::{ + ArchiveEntry, ListArchiveEntriesRequest, ListArchiveEntriesResponse, ObjectType, PageInfo, + object_selector, +}; + +impl GitBare { + pub fn list_archive_entries( + &self, + request: ListArchiveEntriesRequest, + ) -> GitResult { + let revision = match request.treeish.and_then(|s| s.selector) { + Some(object_selector::Selector::Oid(oid)) => oid.hex, + Some(object_selector::Selector::Revision(name)) => name.revision, + None => "HEAD".into(), + }; + let mut args = vec!["ls-tree".to_string(), "-r".into(), "-l".into(), revision]; + if !request.pathspec.is_empty() { + args.push("--".into()); + args.extend(request.pathspec); + } + let output = Command::new("git") + .arg("--git-dir") + .arg(&self.bare_dir) + .args(&args) + .output()?; + if !output.status.success() { + return Err(GitError::CommandFailed { + status_code: output.status.code(), + stderr: String::from_utf8_lossy(&output.stderr).into_owned(), + }); + } + let entries = String::from_utf8_lossy(&output.stdout) + .lines() + .filter_map(|line| { + let (meta, path) = line.split_once('\t')?; + let parts: Vec<&str> = meta.split_whitespace().collect(); + let hex = parts.get(2)?.to_string(); + Some(ArchiveEntry { + path: path.to_string(), + oid: Some(self.oid_to_pb(hex)), + mode: u32::from_str_radix(parts.first().copied().unwrap_or("0"), 8) + .unwrap_or(0), + size: parts.get(3).and_then(|s| s.parse().ok()).unwrap_or(0), + r#type: match parts.get(1).copied().unwrap_or_default() { + "tree" => ObjectType::Tree as i32, + "blob" => ObjectType::Blob as i32, + "commit" => ObjectType::Commit as i32, + "tag" => ObjectType::Tag as i32, + _ => ObjectType::Unspecified as i32, + }, + }) + }) + .collect::>(); + let total_count = entries.len() as u64; + Ok(ListArchiveEntriesResponse { + entries, + page_info: Some(PageInfo { + next_page_token: String::new(), + has_next_page: false, + total_count, + }), + }) + } +} diff --git a/archive/mod.rs b/archive/mod.rs new file mode 100644 index 0000000..ddd2882 --- /dev/null +++ b/archive/mod.rs @@ -0,0 +1,2 @@ +pub mod get_archive; +pub mod list_archive_entries; diff --git a/bare.rs b/bare.rs new file mode 100644 index 0000000..edd5e73 --- /dev/null +++ b/bare.rs @@ -0,0 +1,112 @@ +use std::path::{Path, PathBuf}; + +use crate::error::{GitError, GitResult}; +use crate::pb::RepositoryHeader; + +pub struct GitBare { + pub bare_dir: PathBuf, +} + +impl GitBare { + pub fn gix_repo(&self) -> GitResult { + gix::open(&self.bare_dir) + .map_err(|e| GitError::Internal(format!("failed to open gix repository: {e}"))) + } + + pub fn from_repository_header(header: &RepositoryHeader) -> GitResult { + let storage_path = header.storage_path.trim(); + let relative_path = header.relative_path.trim(); + let storage_name = header.storage_name.trim(); + let _ = storage_name; // reserved for future sharding logic + + // Build base path: storage_path if given, else relative_path alone + let base = if !storage_path.is_empty() { + let p = Path::new(storage_path); + if !p.is_absolute() { + return Err(GitError::InvalidArgument( + "storage_path must be an absolute path".into(), + )); + } + PathBuf::from(p) + } else if !relative_path.is_empty() { + // relative_path alone is rejected unless absolute + return Err(GitError::InvalidArgument( + "relative_path requires storage_path to be set".into(), + )); + } else { + return Err(GitError::InvalidArgument("empty repository path".into())); + }; + + // Join relative_path if provided + let bare_dir = if !relative_path.is_empty() && !storage_path.is_empty() { + let candidate = base.join(relative_path); + // Canonicalize to resolve any `..` / symlinks, then check still under base + let canonical = candidate + .canonicalize() + .unwrap_or_else(|_| candidate.clone()); + // Path traversal check: canonical resolved dir must start with base + let base_canon = base.canonicalize().unwrap_or_else(|_| base.clone()); + if !canonical.starts_with(&base_canon) { + return Err(GitError::InvalidArgument(format!( + "path traversal detected: {relative_path} escapes storage root" + ))); + } + canonical + } else if !storage_path.is_empty() { + base.canonicalize().unwrap_or(base) + } else { + return Err(GitError::InvalidArgument("empty repository path".into())); + }; + + // Validate bare_dir exists, is a directory, and is readable + if !bare_dir.exists() { + return Err(GitError::RepoNotFound); + } + if !bare_dir.is_dir() { + return Err(GitError::InvalidArgument(format!( + "not a directory: {}", + bare_dir.display() + ))); + } + + // Accept either bare repos (HEAD file) or non-bare (HEAD + .git) + let head_path = bare_dir.join("HEAD"); + if !head_path.exists() { + // Maybe it's a non-bare repo + let git_dir = bare_dir.join(".git"); + if git_dir.is_dir() && git_dir.join("HEAD").exists() { + return Ok(Self { bare_dir: git_dir }); + } + return Err(GitError::NotBareRepository); + } + + Ok(Self { bare_dir }) + } + + /// Detect the repository's object format (SHA-1 or SHA-256). + pub fn object_format(&self) -> crate::pb::ObjectFormat { + let repo = self.gix_repo().ok(); + let kind = repo + .map(|r| r.object_hash()) + .unwrap_or(gix::hash::Kind::Sha1); + match kind { + gix::hash::Kind::Sha1 => crate::pb::ObjectFormat::Sha1, + gix::hash::Kind::Sha256 => crate::pb::ObjectFormat::Sha256, + _ => crate::pb::ObjectFormat::Unspecified, + } + } + + /// Convert a hex object id to a protobuf Oid. + /// + /// `Oid.value` is the binary hash bytes, while `Oid.hex` keeps the printable + /// lowercase representation for clients that prefer string IDs. + pub fn oid_to_pb(&self, hex: impl Into) -> crate::pb::Oid { + let hex = hex.into().to_lowercase(); + let format = self.object_format(); + crate::pb::Oid { + value: crate::oid::hex_to_bytes(&hex).unwrap_or_default(), + hex, + format: format as i32, + } + } +} diff --git a/blame/do_blame.rs b/blame/do_blame.rs new file mode 100644 index 0000000..dc98a5c --- /dev/null +++ b/blame/do_blame.rs @@ -0,0 +1,152 @@ +use crate::bare::GitBare; +use crate::error::{GitError, GitResult}; +use crate::pb::{BlameHunk, BlameLine, BlameRequest, BlameResponse, PageInfo}; + +impl GitBare { + pub fn blame(&self, request: BlameRequest) -> GitResult { + let revision = match request.revision.and_then(|s| s.selector) { + Some(crate::pb::object_selector::Selector::Oid(oid)) => oid.hex, + Some(crate::pb::object_selector::Selector::Revision(name)) => name.revision, + None => "HEAD".into(), + }; + let mut args = vec![ + "--git-dir".to_string(), + self.bare_dir.to_string_lossy().into_owned(), + "blame".to_string(), + "--porcelain".to_string(), + ]; + if let Some(range) = &request.range { + args.push("-L".into()); + args.push(format!("{},{}", range.start, range.end)); + } + args.push(revision); + args.push("--".into()); + args.push(request.path.clone()); + let result = duct::cmd("git", &args) + .stdout_capture() + .stderr_capture() + .unchecked() + .run()?; + if !result.status.success() { + return Err(GitError::CommandFailed { + status_code: result.status.code(), + stderr: String::from_utf8_lossy(&result.stderr).into_owned(), + }); + } + let output = String::from_utf8_lossy(&result.stdout); + let hunks = parse_porcelain_blame(&output, &request.path, self); + let total_count = hunks.len() as u64; + Ok(BlameResponse { + hunks, + page_info: Some(PageInfo { + next_page_token: String::new(), + has_next_page: false, + total_count, + }), + truncated: false, + }) + } +} + +fn parse_porcelain_blame(output: &str, path: &str, repo: &GitBare) -> Vec { + let mut hunks = Vec::new(); + let mut current_hunk: Option = None; + + for line in output.lines() { + if let Some(content) = line.strip_prefix('\t') { + if let Some(ref mut hunk) = current_hunk { + let next_line_no = hunk.final_start_line + hunk.lines.len() as u32; + hunk.lines.push(BlameLine { + final_line: next_line_no, + original_line: 0, + content: content.as_bytes().to_vec(), + }); + } + continue; + } + + let parts: Vec<&str> = line.splitn(4, ' ').collect(); + if parts.is_empty() { + continue; + } + + let token = parts[0]; + if token.len() == 40 || token.len() == 64 { + let orig_line: u32 = parts.get(1).and_then(|s| s.parse().ok()).unwrap_or(0); + let final_line: u32 = parts.get(2).and_then(|s| s.parse().ok()).unwrap_or(0); + + if let Some(prev) = current_hunk.take() { + hunks.push(prev); + } + + current_hunk = Some(BlameHunk { + commit: Some(crate::pb::Commit { + oid: Some(repo.oid_to_pb(token)), + abbreviated_oid: token.chars().take(7).collect(), + subject: String::new(), + message: String::new(), + ..Default::default() + }), + original_path: String::new(), + final_path: path.to_string(), + original_start_line: orig_line, + final_start_line: final_line, + line_count: 0, + boundary: false, + lines: Vec::new(), + }); + } else if let Some(ref mut hunk) = current_hunk { + match token { + "author" => { + if let Some(commit) = hunk.commit.as_mut() { + let name = line.strip_prefix("author ").unwrap_or_default(); + if let Some(sig) = commit.author.as_mut() { + if let Some(id) = sig.identity.as_mut() { + id.name = name.to_string(); + } + } else { + commit.author = Some(crate::pb::Signature { + identity: Some(crate::pb::Identity { + name: name.to_string(), + email: String::new(), + }), + ..Default::default() + }); + } + } + } + "author-mail" => { + if let Some(commit) = hunk.commit.as_mut() { + let email = line + .strip_prefix("author-mail ") + .unwrap_or_default() + .trim_matches(|c| c == '<' || c == '>') + .to_string(); + if let Some(sig) = commit.author.as_mut() + && let Some(id) = sig.identity.as_mut() + { + id.email = email; + } + } + } + "filename" => { + hunk.original_path = line.strip_prefix("filename ").unwrap_or(path).to_string(); + } + "boundary" => { + hunk.boundary = true; + } + _ => {} + } + } + } + + if let Some(hunk) = current_hunk { + hunks.push(hunk); + } + + for hunk in &mut hunks { + hunk.line_count = hunk.lines.len() as u32; + } + + hunks +} diff --git a/blame/mod.rs b/blame/mod.rs new file mode 100644 index 0000000..9225936 --- /dev/null +++ b/blame/mod.rs @@ -0,0 +1 @@ +pub mod do_blame; diff --git a/blob/get_blob.rs b/blob/get_blob.rs new file mode 100644 index 0000000..d2e39c5 --- /dev/null +++ b/blob/get_blob.rs @@ -0,0 +1,70 @@ +use gix::object::tree::EntryKind; + +use crate::bare::GitBare; +use crate::error::{GitError, GitResult}; +use crate::pb::{Blob, GetBlobRequest, object_selector}; + +impl GitBare { + pub fn get_blob(&self, request: GetBlobRequest) -> GitResult { + let repo = self.gix_repo()?; + let (blob, mode, path) = if let Some(oid) = request.oid.as_ref() { + let id = gix::hash::ObjectId::from_hex(oid.hex.as_bytes()) + .map_err(|e| GitError::InvalidOid(e.to_string()))?; + ( + repo.find_object(id)? + .try_into_blob() + .map_err(|e| GitError::Gix(e.to_string()))?, + 0, + request.path, + ) + } else { + let revision = match request.revision.and_then(|s| s.selector) { + Some(object_selector::Selector::Oid(oid)) => oid.hex, + Some(object_selector::Selector::Revision(name)) => name.revision, + None => "HEAD".into(), + }; + let tree = repo + .rev_parse_single(format!("{}^{{tree}}", revision).as_str())? + .object()? + .try_into_tree() + .map_err(|e| GitError::Gix(e.to_string()))?; + let entry = tree + .lookup_entry_by_path(&request.path)? + .ok_or_else(|| GitError::NotFound(request.path.clone()))?; + let mode = u32::from_str_radix(&format!("{:o}", entry.mode()), 8).unwrap_or(0); + if !matches!( + entry.mode().kind(), + EntryKind::Blob | EntryKind::BlobExecutable | EntryKind::Link + ) { + return Err(GitError::InvalidArgument( + "path does not point to a blob".into(), + )); + } + ( + entry + .object()? + .try_into_blob() + .map_err(|e| GitError::Gix(e.to_string()))?, + mode, + request.path, + ) + }; + let original_size = blob.data.len() as i64; + let mut data = blob.data.clone(); + let truncated = request.max_bytes > 0 && data.len() > request.max_bytes as usize; + if truncated { + data.truncate(request.max_bytes as usize); + } + let hex = blob.id.to_string(); + Ok(Blob { + oid: Some(self.oid_to_pb(hex)), + path, + mode, + size: original_size, + binary: blob.data.contains(&0), + encoding: String::new(), + truncated, + data, + }) + } +} diff --git a/blob/get_raw_blob.rs b/blob/get_raw_blob.rs new file mode 100644 index 0000000..ca54496 --- /dev/null +++ b/blob/get_raw_blob.rs @@ -0,0 +1,16 @@ +use crate::bare::GitBare; +use crate::error::GitResult; +use crate::pb::{GetBlobRequest, GetRawBlobRequest, GetRawBlobResponse}; + +impl GitBare { + pub fn get_raw_blob(&self, request: GetRawBlobRequest) -> GitResult> { + let blob = self.get_blob(GetBlobRequest { + repository: request.repository, + revision: request.revision, + path: request.path, + oid: request.oid, + max_bytes: 0, + })?; + Ok(vec![GetRawBlobResponse { data: blob.data }]) + } +} diff --git a/blob/mod.rs b/blob/mod.rs new file mode 100644 index 0000000..b47e4d9 --- /dev/null +++ b/blob/mod.rs @@ -0,0 +1,2 @@ +pub mod get_blob; +pub mod get_raw_blob; diff --git a/branch/compare_branch.rs b/branch/compare_branch.rs new file mode 100644 index 0000000..e3ef994 --- /dev/null +++ b/branch/compare_branch.rs @@ -0,0 +1,54 @@ +use crate::bare::GitBare; +use crate::error::{GitError, GitResult}; +use crate::pb::{CompareBranchRequest, CompareBranchResponse}; + +impl GitBare { + pub fn compare_branch( + &self, + request: CompareBranchRequest, + ) -> GitResult { + let repo = self.gix_repo()?; + let source_ref = format!("refs/heads/{}", request.source_branch); + let target_ref = format!("refs/heads/{}", request.target_branch); + let source_id = repo.find_reference(source_ref.as_str())?.peel_to_id()?; + let target_id = repo.find_reference(target_ref.as_str())?.peel_to_id()?; + let source_hex = source_id.to_string(); + let target_hex = target_id.to_string(); + let merge_base = repo + .merge_base(source_id.detach(), target_id.detach()) + .ok() + .map(|id| self.oid_to_pb(id.to_string())); + let result = duct::cmd( + "git", + [ + "--git-dir", + self.bare_dir.to_string_lossy().as_ref(), + "rev-list", + "--left-right", + "--count", + &format!("{}...{}", source_hex, target_hex), + ], + ) + .stdout_capture() + .stderr_capture() + .unchecked() + .run()?; + if !result.status.success() { + return Err(GitError::CommandFailed { + status_code: result.status.code(), + stderr: String::from_utf8_lossy(&result.stderr).into_owned(), + }); + } + let output = String::from_utf8_lossy(&result.stdout); + let parts: Vec<&str> = output.split_whitespace().collect(); + let ahead_by = parts.first().and_then(|s| s.parse().ok()).unwrap_or(0); + let behind_by = parts.get(1).and_then(|s| s.parse().ok()).unwrap_or(0); + Ok(CompareBranchResponse { + ahead: ahead_by > 0, + behind: behind_by > 0, + ahead_by, + behind_by, + merge_base, + }) + } +} diff --git a/branch/create_branch.rs b/branch/create_branch.rs new file mode 100644 index 0000000..807a1cb --- /dev/null +++ b/branch/create_branch.rs @@ -0,0 +1,36 @@ +use std::process::Command; + +use crate::bare::GitBare; +use crate::error::{GitError, GitResult}; +use crate::pb::{Branch, CreateBranchRequest, GetBranchRequest, object_selector}; + +impl GitBare { + pub fn create_branch(&self, request: CreateBranchRequest) -> GitResult { + let revision = match request.start_point.and_then(|s| s.selector) { + Some(object_selector::Selector::Oid(oid)) => oid.hex, + Some(object_selector::Selector::Revision(name)) => name.revision, + None => "HEAD".into(), + }; + let mut args = vec!["branch".to_string()]; + if request.force { + args.push("-f".into()); + } + args.push(request.name.clone()); + args.push(revision); + let output = Command::new("git") + .arg("--git-dir") + .arg(&self.bare_dir) + .args(&args) + .output()?; + if !output.status.success() { + return Err(GitError::CommandFailed { + status_code: output.status.code(), + stderr: String::from_utf8_lossy(&output.stderr).into_owned(), + }); + } + self.get_branch(GetBranchRequest { + repository: request.repository, + name: request.name, + }) + } +} diff --git a/branch/delete_branch.rs b/branch/delete_branch.rs new file mode 100644 index 0000000..6c55284 --- /dev/null +++ b/branch/delete_branch.rs @@ -0,0 +1,23 @@ +use std::process::Command; + +use crate::bare::GitBare; +use crate::error::{GitError, GitResult}; +use crate::pb::DeleteBranchRequest; + +impl GitBare { + pub fn delete_branch(&self, request: DeleteBranchRequest) -> GitResult<()> { + let flag = if request.force { "-D" } else { "-d" }; + let output = Command::new("git") + .arg("--git-dir") + .arg(&self.bare_dir) + .args(["branch", flag, &request.name]) + .output()?; + if !output.status.success() { + return Err(GitError::CommandFailed { + status_code: output.status.code(), + stderr: String::from_utf8_lossy(&output.stderr).into_owned(), + }); + } + Ok(()) + } +} diff --git a/branch/get_branch.rs b/branch/get_branch.rs new file mode 100644 index 0000000..b737f4a --- /dev/null +++ b/branch/get_branch.rs @@ -0,0 +1,23 @@ +use crate::bare::GitBare; +use crate::error::GitResult; +use crate::pb::{Branch, GetBranchRequest}; + +impl GitBare { + pub fn get_branch(&self, request: GetBranchRequest) -> GitResult { + let repo = self.gix_repo()?; + let refname = format!("refs/heads/{}", request.name); + let mut r = repo.find_reference(refname.as_str())?; + let hex = r.peel_to_id()?.to_string(); + Ok(Branch { + name: request.name, + full_ref: refname, + target_oid: Some(self.oid_to_pb(hex)), + commit: None, + upstream: None, + is_default: false, + is_head: false, + is_merged: false, + is_detached: false, + }) + } +} diff --git a/branch/list_branches.rs b/branch/list_branches.rs new file mode 100644 index 0000000..b72253a --- /dev/null +++ b/branch/list_branches.rs @@ -0,0 +1,78 @@ +use crate::bare::GitBare; +use crate::error::GitResult; +use crate::paginate; +use crate::pb::{Branch, ListBranchesRequest, ListBranchesResponse}; + +impl GitBare { + pub fn list_branches(&self, request: ListBranchesRequest) -> GitResult { + let repo = self.gix_repo()?; + + let merged_set = if request.merged_into_head || request.not_merged_into_head { + let flag = if request.merged_into_head { + "--merged" + } else { + "--no-merged" + }; + let check = duct::cmd( + "git", + [ + "--git-dir", + self.bare_dir.to_string_lossy().as_ref(), + "branch", + flag, + "HEAD", + ], + ) + .stdout_capture() + .stderr_capture() + .unchecked() + .run(); + match check { + Ok(out) => String::from_utf8_lossy(&out.stdout) + .lines() + .map(|l| l.trim().trim_start_matches("* ").to_string()) + .collect::>(), + Err(_) => Vec::new(), + } + } else { + Vec::new() + }; + + let mut branches: Vec = Vec::new(); + for r in repo.references()?.local_branches()? { + let mut r = r.map_err(|e| crate::error::GitError::Gix(e.to_string()))?; + let name = r.name().shorten().to_string(); + if !request.pattern.is_empty() && !name.contains(&request.pattern) { + continue; + } + if request.merged_into_head && !merged_set.contains(&name) { + continue; + } + if request.not_merged_into_head && merged_set.contains(&name) { + continue; + } + let hex = r + .peel_to_id() + .ok() + .map(|id| id.to_string()) + .unwrap_or_default(); + branches.push(Branch { + name, + full_ref: r.name().to_string(), + target_oid: Some(self.oid_to_pb(hex)), + commit: None, + upstream: None, + is_default: false, + is_head: false, + is_merged: false, + is_detached: false, + }); + } + paginate::apply_sort(&mut branches, request.sort_direction); + let (branches, page_info) = paginate::paginate(&branches, request.pagination.as_ref()); + Ok(ListBranchesResponse { + branches, + page_info: Some(page_info), + }) + } +} diff --git a/branch/mod.rs b/branch/mod.rs new file mode 100644 index 0000000..c0c2e6e --- /dev/null +++ b/branch/mod.rs @@ -0,0 +1,8 @@ +pub mod compare_branch; +pub mod create_branch; +pub mod delete_branch; +pub mod get_branch; +pub mod list_branches; +pub mod rename_branch; +pub mod set_branch_upstream; +pub mod update_branch_target; diff --git a/branch/rename_branch.rs b/branch/rename_branch.rs new file mode 100644 index 0000000..010b213 --- /dev/null +++ b/branch/rename_branch.rs @@ -0,0 +1,25 @@ +use std::process::Command; + +use crate::bare::GitBare; +use crate::error::{GitError, GitResult}; +use crate::pb::{Branch, GetBranchRequest, RenameBranchRequest}; + +impl GitBare { + pub fn rename_branch(&self, request: RenameBranchRequest) -> GitResult { + let output = Command::new("git") + .arg("--git-dir") + .arg(&self.bare_dir) + .args(["branch", "-m", &request.old_name, &request.new_name]) + .output()?; + if !output.status.success() { + return Err(GitError::CommandFailed { + status_code: output.status.code(), + stderr: String::from_utf8_lossy(&output.stderr).into_owned(), + }); + } + self.get_branch(GetBranchRequest { + repository: request.repository, + name: request.new_name, + }) + } +} diff --git a/branch/set_branch_upstream.rs b/branch/set_branch_upstream.rs new file mode 100644 index 0000000..21acedd --- /dev/null +++ b/branch/set_branch_upstream.rs @@ -0,0 +1,36 @@ +use crate::bare::GitBare; +use crate::error::{GitError, GitResult}; +use crate::pb::{Branch, GetBranchRequest, SetBranchUpstreamRequest}; + +impl GitBare { + pub fn set_branch_upstream(&self, request: SetBranchUpstreamRequest) -> GitResult { + if let Some(upstream) = request.upstream { + let tracking = format!("{}/{}", upstream.remote_name, upstream.remote_branch_name); + let result = duct::cmd( + "git", + [ + "--git-dir", + self.bare_dir.to_string_lossy().as_ref(), + "branch", + "--set-upstream-to", + &tracking, + &request.name, + ], + ) + .stdout_capture() + .stderr_capture() + .unchecked() + .run()?; + if !result.status.success() { + return Err(GitError::CommandFailed { + status_code: result.status.code(), + stderr: String::from_utf8_lossy(&result.stderr).into_owned(), + }); + } + } + self.get_branch(GetBranchRequest { + repository: request.repository, + name: request.name, + }) + } +} diff --git a/branch/update_branch_target.rs b/branch/update_branch_target.rs new file mode 100644 index 0000000..55675b1 --- /dev/null +++ b/branch/update_branch_target.rs @@ -0,0 +1,39 @@ +#![allow(clippy::collapsible_if)] +use std::process::Command; + +use crate::bare::GitBare; +use crate::error::{GitError, GitResult}; +use crate::pb::{Branch, GetBranchRequest, UpdateBranchTargetRequest}; + +impl GitBare { + pub fn update_branch_target(&self, request: UpdateBranchTargetRequest) -> GitResult { + let new_oid = request + .new_oid + .as_ref() + .ok_or_else(|| GitError::InvalidArgument("new_oid is required".into()))? + .hex + .clone(); + let refname = format!("refs/heads/{}", request.name); + let mut args = vec!["update-ref".to_string(), refname.clone(), new_oid]; + if !request.force + && let Some(old) = request.expected_old_oid.as_ref() + { + args.push(old.hex.clone()); + } + let output = Command::new("git") + .arg("--git-dir") + .arg(&self.bare_dir) + .args(&args) + .output()?; + if !output.status.success() { + return Err(GitError::CommandFailed { + status_code: output.status.code(), + stderr: String::from_utf8_lossy(&output.stderr).into_owned(), + }); + } + self.get_branch(GetBranchRequest { + repository: request.repository, + name: refname.trim_start_matches("refs/heads/").to_string(), + }) + } +} diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..eb690ce --- /dev/null +++ b/build.rs @@ -0,0 +1,51 @@ +use std::fs; +use std::path::{Path, PathBuf}; + +fn main() -> Result<(), Box> { + let manifest_dir = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR")?); + let proto_dir = manifest_dir.join("proto"); + let out_dir = PathBuf::from(std::env::var("OUT_DIR")?); + + fs::create_dir_all(&out_dir)?; + clean_generated_files(&out_dir)?; + + let protos = proto_files(&proto_dir)?; + for proto in &protos { + println!("cargo:rerun-if-changed={}", proto.display()); + } + println!("cargo:rerun-if-changed={}", proto_dir.display()); + println!("cargo:rerun-if-changed=build.rs"); + + tonic_build::configure() + .build_client(true) + .build_server(true) + .emit_rerun_if_changed(false) + .out_dir(&out_dir) + .compile_protos(&protos, &[proto_dir])?; + + Ok(()) +} + +fn proto_files(proto_dir: &Path) -> Result, Box> { + let mut files = fs::read_dir(proto_dir)? + .map(|entry| entry.map(|entry| entry.path())) + .collect::, _>>()?; + + files.retain(|path| path.extension().is_some_and(|ext| ext == "proto")); + files.sort(); + Ok(files) +} + +fn clean_generated_files(out_dir: &Path) -> Result<(), Box> { + for entry in fs::read_dir(out_dir)? { + let path = entry?.path(); + let is_generated_rs = path.extension().is_some_and(|ext| ext == "rs") + && path.file_name().is_some_and(|name| name != "mod.rs"); + + if is_generated_rs { + fs::remove_file(path)?; + } + } + + Ok(()) +} diff --git a/commit/cherry_pick_commit.rs b/commit/cherry_pick_commit.rs new file mode 100644 index 0000000..fdf1690 --- /dev/null +++ b/commit/cherry_pick_commit.rs @@ -0,0 +1,161 @@ +use crate::bare::GitBare; +use crate::commit::create_commit::command_ok; +use crate::error::{GitError, GitResult}; +use crate::pb::{CherryPickCommitRequest, CreateCommitResponse, GetCommitRequest}; + +impl GitBare { + pub fn cherry_pick_commit( + &self, + request: CherryPickCommitRequest, + ) -> GitResult { + let target_branch = request.branch.clone(); + let cp_revision = match request.commit.and_then(|s| s.selector) { + Some(crate::pb::object_selector::Selector::Oid(oid)) => oid.hex, + Some(crate::pb::object_selector::Selector::Revision(name)) => name.revision, + None => return Err(GitError::InvalidArgument("commit is required".into())), + }; + + let repo = self.gix_repo()?; + + let branch_ref = format!("refs/heads/{}", target_branch); + let branch_tip = repo + .find_reference(branch_ref.as_str()) + .ok() + .and_then(|mut r| r.peel_to_id().ok()) + .map(|id| id.to_string()) + .ok_or_else(|| GitError::RefNotFound(target_branch.clone()))?; + + let cp_id = repo.rev_parse_single(cp_revision.as_str())?; + let cp_obj = cp_id + .object()? + .try_into_commit() + .map_err(|e| GitError::Gix(e.to_string()))?; + let parent_id = cp_obj.parent_ids().next().map(|p| p.to_string()); + + let tmp_index = tempfile::Builder::new() + .prefix("gitks-cp-") + .tempfile_in(&self.bare_dir)?; + let idx_path = tmp_index.path().to_string_lossy().into_owned(); + let bare = self.bare_dir.to_string_lossy().into_owned(); + + let read_tree = duct::cmd( + "git", + ["--git-dir", bare.as_str(), "read-tree", branch_tip.as_str()], + ) + .env("GIT_INDEX_FILE", &idx_path) + .stdout_capture() + .stderr_capture() + .unchecked() + .run()?; + command_ok(read_tree)?; + + let mut format_patch_args = vec![ + "--git-dir".to_string(), + bare.clone(), + "format-patch".to_string(), + "--stdout".to_string(), + "--full-index".to_string(), + "--binary".to_string(), + "-1".to_string(), + ]; + if parent_id.is_none() { + format_patch_args.push("--root".to_string()); + } + format_patch_args.push(cp_revision.clone()); + + let diff = duct::cmd("git", &format_patch_args) + .env("GIT_INDEX_FILE", &idx_path) + .stdout_capture() + .stderr_capture() + .unchecked() + .run()?; + let patch_data = command_ok(diff)?; + + let apply = duct::cmd( + "git", + [ + "--git-dir", + bare.as_str(), + "apply", + "--cached", + "--allow-empty", + "-", + ], + ) + .env("GIT_INDEX_FILE", &idx_path) + .stdin_bytes(patch_data.as_bytes()) + .stdout_capture() + .stderr_capture() + .unchecked() + .run()?; + if !apply.status.success() { + return Err(GitError::Internal(format!( + "cherry-pick apply failed: {}", + String::from_utf8_lossy(&apply.stderr) + ))); + } + + let write_tree = duct::cmd("git", ["--git-dir", bare.as_str(), "write-tree"]) + .env("GIT_INDEX_FILE", &idx_path) + .stdout_capture() + .stderr_capture() + .unchecked() + .run()?; + let tree_id = command_ok(write_tree)?.trim().to_string(); + + let message = cp_obj.message_raw()?.to_string(); + + let parents = vec![branch_tip.clone()]; + let commit_id = self.commit_tree( + &tree_id, + &parents, + &message, + request.committer.as_ref(), + request.committer.as_ref(), + )?; + + self.update_branch_ref(&target_branch, &commit_id, Some(&branch_tip), false)?; + + Ok(CreateCommitResponse { + commit: Some(self.get_commit(GetCommitRequest { + repository: request.repository, + revision: Some(crate::pb::ObjectSelector { + selector: Some(crate::pb::object_selector::Selector::Revision( + crate::pb::ObjectName { + revision: commit_id, + }, + )), + }), + include_stats: false, + include_raw: false, + })?), + branch: target_branch, + }) + } + + pub(crate) fn update_branch_ref( + &self, + branch: &str, + commit_id: &str, + old_value: Option<&str>, + force: bool, + ) -> GitResult<()> { + let refname = format!("refs/heads/{}", branch); + let mut args = vec![ + "--git-dir".to_string(), + self.bare_dir.to_string_lossy().into_owned(), + "update-ref".into(), + refname, + commit_id.to_string(), + ]; + if !force { + args.push(old_value.unwrap_or(crate::oid::ZERO_OID).to_string()); + } + let update = duct::cmd("git", &args) + .stdout_capture() + .stderr_capture() + .unchecked() + .run()?; + command_ok(update).map(|_| ()) + } +} diff --git a/commit/compare_commits.rs b/commit/compare_commits.rs new file mode 100644 index 0000000..ac213d8 --- /dev/null +++ b/commit/compare_commits.rs @@ -0,0 +1,94 @@ +use crate::bare::GitBare; +use crate::diff::get_diff_stats::diff_stats_for_range; +use crate::error::{GitError, GitResult}; +use crate::paginate; +use crate::pb::{ + CommitStats, CompareCommitsRequest, CompareCommitsResponse, GetCommitRequest, object_selector, +}; + +impl GitBare { + pub fn compare_commits( + &self, + request: CompareCommitsRequest, + ) -> GitResult { + let repo = self.gix_repo()?; + let base = match request.base.clone().and_then(|s| s.selector) { + Some(object_selector::Selector::Oid(oid)) => oid.hex, + Some(object_selector::Selector::Revision(name)) => name.revision, + None => "HEAD".into(), + }; + let head = match request.head.clone().and_then(|s| s.selector) { + Some(object_selector::Selector::Oid(oid)) => oid.hex, + Some(object_selector::Selector::Revision(name)) => name.revision, + None => "HEAD".into(), + }; + + let base_id = repo.rev_parse_single(base.as_str())?; + let head_id = repo.rev_parse_single(head.as_str())?; + let merge_base = repo + .merge_base(base_id.detach(), head_id.detach()) + .ok() + .map(|id| self.oid_to_pb(id.to_string())); + + let range = if request.straight { + format!("{base}..{head}") + } else { + format!("{base}...{head}") + }; + let mut args = vec![ + "--git-dir".to_string(), + self.bare_dir.to_string_lossy().into_owned(), + "rev-list".into(), + ]; + if request.first_parent { + args.push("--first-parent".into()); + } + args.push(range); + + let rev_list = duct::cmd("git", &args) + .stdout_capture() + .stderr_capture() + .unchecked() + .run()?; + if !rev_list.status.success() { + return Err(GitError::CommandFailed { + status_code: rev_list.status.code(), + stderr: String::from_utf8_lossy(&rev_list.stderr).into_owned(), + }); + } + + let ids = String::from_utf8_lossy(&rev_list.stdout) + .lines() + .map(str::trim) + .filter(|line| !line.is_empty()) + .map(ToOwned::to_owned) + .collect::>(); + let (page_ids, page_info) = paginate::paginate(&ids, request.pagination.as_ref()); + + let mut commits = Vec::with_capacity(page_ids.len()); + for id in page_ids { + commits.push(self.get_commit(GetCommitRequest { + repository: request.repository.clone(), + revision: Some(crate::pb::ObjectSelector { + selector: Some(object_selector::Selector::Revision(crate::pb::ObjectName { + revision: id, + })), + }), + include_stats: false, + include_raw: false, + })?); + } + + let diff_stats = diff_stats_for_range(self, &base, &head, None)?; + Ok(CompareCommitsResponse { + commits, + stats: Some(CommitStats { + additions: diff_stats.additions, + deletions: diff_stats.deletions, + changed_files: diff_stats.changed_files, + }), + page_info: Some(page_info), + merge_base, + }) + } +} diff --git a/commit/create_commit.rs b/commit/create_commit.rs new file mode 100644 index 0000000..7a544ea --- /dev/null +++ b/commit/create_commit.rs @@ -0,0 +1,375 @@ +use crate::bare::GitBare; +use crate::error::{GitError, GitResult}; +use crate::oid::ZERO_OID; +use crate::pb::{ + CreateCommitRequest, CreateCommitResponse, GetCommitRequest, ObjectName, ObjectSelector, + create_commit_action, object_selector, +}; + +impl GitBare { + pub fn create_commit(&self, request: CreateCommitRequest) -> GitResult { + let repo = self.gix_repo()?; + let start_rev = match request.start_revision.clone().and_then(|s| s.selector) { + Some(object_selector::Selector::Oid(oid)) => oid.hex, + Some(object_selector::Selector::Revision(name)) => name.revision, + None => request.branch.clone(), + }; + let parent_id = repo + .rev_parse_single(start_rev.as_str()) + .ok() + .map(|id| id.to_string()); + let current_branch_tip = repo + .find_reference(format!("refs/heads/{}", request.branch).as_str()) + .ok() + .and_then(|mut r| r.peel_to_id().ok()) + .map(|id| id.to_string()); + + let tree_id = if request.actions.is_empty() { + let Some(parent) = parent_id.as_ref() else { + return Err(GitError::InvalidArgument( + "cannot create an empty root commit without file actions".into(), + )); + }; + self.rev_parse_tree(parent)? + } else { + self.tree_from_actions(parent_id.as_deref(), &request.actions)? + }; + + let message = commit_message_with_trailers(&request); + let parents: Vec = parent_id.iter().cloned().collect(); + let commit_id = self.commit_tree( + &tree_id, + &parents, + &message, + request.author.as_ref(), + request.committer.as_ref(), + )?; + self.update_branch_after_commit(&request, &commit_id, current_branch_tip.as_deref())?; + + let revision = Some(ObjectSelector { + selector: Some(object_selector::Selector::Revision(ObjectName { + revision: commit_id, + })), + }); + Ok(CreateCommitResponse { + commit: Some(self.get_commit(GetCommitRequest { + repository: request.repository, + revision, + include_stats: false, + include_raw: false, + })?), + branch: request.branch, + }) + } + + fn rev_parse_tree(&self, revision: &str) -> GitResult { + let result = duct::cmd( + "git", + [ + "--git-dir", + self.bare_dir.to_string_lossy().as_ref(), + "rev-parse", + &format!("{revision}^{{tree}}"), + ], + ) + .stdout_capture() + .stderr_capture() + .unchecked() + .run()?; + command_ok(result).map(|stdout| stdout.trim().to_string()) + } + + fn tree_from_actions( + &self, + parent_id: Option<&str>, + actions: &[crate::pb::CreateCommitAction], + ) -> GitResult { + let tmp_index = tempfile::Builder::new() + .prefix("gitks-index-") + .tempfile_in(&self.bare_dir) + .map_err(GitError::Io)?; + let tmp_index_path = tmp_index.path().to_string_lossy().into_owned(); + + if let Some(parent) = parent_id { + let read_tree = duct::cmd( + "git", + [ + "--git-dir", + self.bare_dir.to_string_lossy().as_ref(), + "read-tree", + parent, + ], + ) + .env("GIT_INDEX_FILE", &tmp_index_path) + .stdout_capture() + .stderr_capture() + .unchecked() + .run()?; + command_ok(read_tree)?; + } + + for action in actions { + self.apply_commit_action(&tmp_index_path, action)?; + } + + let write_tree = duct::cmd( + "git", + [ + "--git-dir", + self.bare_dir.to_string_lossy().as_ref(), + "write-tree", + ], + ) + .env("GIT_INDEX_FILE", &tmp_index_path) + .stdout_capture() + .stderr_capture() + .unchecked() + .run()?; + command_ok(write_tree).map(|stdout| stdout.trim().to_string()) + } + + fn apply_commit_action( + &self, + index_path: &str, + action: &crate::pb::CreateCommitAction, + ) -> GitResult<()> { + let action_type = create_commit_action::Action::try_from(action.action) + .unwrap_or(create_commit_action::Action::CreateCommitActionUnspecified); + match action_type { + create_commit_action::Action::CreateCommitActionCreate + | create_commit_action::Action::CreateCommitActionUpdate => { + let hash = duct::cmd( + "git", + [ + "--git-dir", + self.bare_dir.to_string_lossy().as_ref(), + "hash-object", + "-w", + "--stdin", + ], + ) + .stdin_bytes(action.content.clone()) + .stdout_capture() + .stderr_capture() + .unchecked() + .run()?; + let blob = command_ok(hash)?.trim().to_string(); + let mode = if action.executable { + "100755" + } else { + "100644" + }; + self.update_index_cacheinfo(index_path, mode, &blob, &action.file_path) + } + create_commit_action::Action::CreateCommitActionDelete => { + let result = duct::cmd( + "git", + [ + "--git-dir", + self.bare_dir.to_string_lossy().as_ref(), + "update-index", + "--force-remove", + &action.file_path, + ], + ) + .env("GIT_INDEX_FILE", index_path) + .env("GIT_WORK_TREE", self.bare_dir.to_string_lossy().as_ref()) + .stdout_capture() + .stderr_capture() + .unchecked() + .run()?; + command_ok(result).map(|_| ()) + } + create_commit_action::Action::CreateCommitActionMove => { + if action.previous_path.is_empty() { + return Err(GitError::InvalidArgument( + "MOVE action requires previous_path".into(), + )); + } + let (mode, oid) = self.index_entry(index_path, &action.previous_path)?; + self.update_index_cacheinfo(index_path, &mode, &oid, &action.file_path)?; + let remove = duct::cmd( + "git", + [ + "--git-dir", + self.bare_dir.to_string_lossy().as_ref(), + "update-index", + "--force-remove", + &action.previous_path, + ], + ) + .env("GIT_INDEX_FILE", index_path) + .env("GIT_WORK_TREE", self.bare_dir.to_string_lossy().as_ref()) + .stdout_capture() + .stderr_capture() + .unchecked() + .run()?; + command_ok(remove).map(|_| ()) + } + create_commit_action::Action::CreateCommitActionChmod => { + let (_old_mode, oid) = self.index_entry(index_path, &action.file_path)?; + let mode = if action.executable { + "100755" + } else { + "100644" + }; + self.update_index_cacheinfo(index_path, mode, &oid, &action.file_path) + } + create_commit_action::Action::CreateCommitActionUnspecified => Err( + GitError::InvalidArgument("unspecified commit action".into()), + ), + } + } + + fn index_entry(&self, index_path: &str, path: &str) -> GitResult<(String, String)> { + let result = duct::cmd( + "git", + [ + "--git-dir", + self.bare_dir.to_string_lossy().as_ref(), + "ls-files", + "-s", + "--", + path, + ], + ) + .env("GIT_INDEX_FILE", index_path) + .env("GIT_WORK_TREE", self.bare_dir.to_string_lossy().as_ref()) + .stdout_capture() + .stderr_capture() + .unchecked() + .run()?; + let stdout = command_ok(result)?; + let line = stdout + .lines() + .next() + .ok_or_else(|| GitError::NotFound(path.to_string()))?; + let parts = line.split_whitespace().collect::>(); + if parts.len() < 2 { + return Err(GitError::ParseError(format!( + "invalid index entry for {path}: {line}" + ))); + } + Ok((parts[0].to_string(), parts[1].to_string())) + } + + fn update_index_cacheinfo( + &self, + index_path: &str, + mode: &str, + oid: &str, + path: &str, + ) -> GitResult<()> { + let result = duct::cmd( + "git", + [ + "--git-dir", + self.bare_dir.to_string_lossy().as_ref(), + "update-index", + "--add", + "--cacheinfo", + mode, + oid, + path, + ], + ) + .env("GIT_INDEX_FILE", index_path) + .env("GIT_WORK_TREE", self.bare_dir.to_string_lossy().as_ref()) + .stdout_capture() + .stderr_capture() + .unchecked() + .run()?; + command_ok(result).map(|_| ()) + } + + pub(crate) fn commit_tree( + &self, + tree_id: &str, + parent_ids: &[String], + message: &str, + author: Option<&crate::pb::Signature>, + committer: Option<&crate::pb::Signature>, + ) -> GitResult { + let mut args = vec![ + "--git-dir".to_string(), + self.bare_dir.to_string_lossy().into_owned(), + "commit-tree".into(), + tree_id.to_string(), + ]; + for parent in parent_ids { + args.push("-p".into()); + args.push(parent.clone()); + } + args.push("-m".into()); + args.push(message.to_string()); + + let mut cmd = duct::cmd("git", &args).stdout_capture().stderr_capture(); + if let Some(author) = author.and_then(|s| s.identity.as_ref()) { + cmd = cmd + .env("GIT_AUTHOR_NAME", &author.name) + .env("GIT_AUTHOR_EMAIL", &author.email); + } + if let Some(committer) = committer.and_then(|s| s.identity.as_ref()) { + cmd = cmd + .env("GIT_COMMITTER_NAME", &committer.name) + .env("GIT_COMMITTER_EMAIL", &committer.email); + } + + let commit = cmd.unchecked().run()?; + command_ok(commit).map(|stdout| stdout.trim().to_string()) + } + + fn update_branch_after_commit( + &self, + request: &CreateCommitRequest, + commit_id: &str, + current_branch_tip: Option<&str>, + ) -> GitResult<()> { + let refname = format!("refs/heads/{}", request.branch); + let mut args = vec![ + "--git-dir".to_string(), + self.bare_dir.to_string_lossy().into_owned(), + "update-ref".into(), + refname, + commit_id.to_string(), + ]; + if !request.force { + args.push(current_branch_tip.unwrap_or(ZERO_OID).to_string()); + } + + let update = duct::cmd("git", &args) + .stdout_capture() + .stderr_capture() + .unchecked() + .run()?; + command_ok(update).map(|_| ()) + } +} + +fn commit_message_with_trailers(request: &CreateCommitRequest) -> String { + if request.trailers.is_empty() { + return request.message.clone(); + } + + let mut message = request.message.trim_end().to_string(); + message.push_str("\n\n"); + for trailer in &request.trailers { + let separator = if trailer.separator_present { ": " } else { " " }; + message.push_str(&trailer.key); + message.push_str(separator); + message.push_str(&trailer.value); + message.push('\n'); + } + message +} + +pub(crate) fn command_ok(output: std::process::Output) -> GitResult { + if output.status.success() { + Ok(String::from_utf8_lossy(&output.stdout).into_owned()) + } else { + Err(GitError::CommandFailed { + status_code: output.status.code(), + stderr: String::from_utf8_lossy(&output.stderr).into_owned(), + }) + } +} diff --git a/commit/get_commit.rs b/commit/get_commit.rs new file mode 100644 index 0000000..02d1853 --- /dev/null +++ b/commit/get_commit.rs @@ -0,0 +1,76 @@ +use crate::bare::GitBare; +use crate::error::{GitError, GitResult}; +use crate::pb::{Commit, GetCommitRequest, object_selector}; + +impl GitBare { + pub fn get_commit(&self, request: GetCommitRequest) -> GitResult { + let repo = self.gix_repo()?; + let revision = match request.revision.and_then(|s| s.selector) { + Some(object_selector::Selector::Oid(oid)) => oid.hex, + Some(object_selector::Selector::Revision(name)) => name.revision, + None => "HEAD".into(), + }; + let id = repo.rev_parse_single(revision.as_str())?; + let commit = id + .object()? + .try_into_commit() + .map_err(|e| GitError::Gix(e.to_string()))?; + let hex = commit.id.to_string(); + let tree_hex = commit.tree_id()?.to_string(); + let message = commit.message_raw()?.to_string(); + let (subject, body) = message + .split_once('\n') + .map(|(s, b)| (s.to_string(), b.trim_start_matches('\n').to_string())) + .unwrap_or_else(|| (message.clone(), String::new())); + let author_sig = commit.author().ok(); + let committer_sig = commit.committer().ok(); + Ok(Commit { + oid: Some(self.oid_to_pb(hex.clone())), + abbreviated_oid: commit + .short_id() + .map(|s| s.to_string()) + .unwrap_or_else(|_| hex.chars().take(7).collect()), + parent_oids: commit + .parent_ids() + .map(|p| self.oid_to_pb(p.to_string())) + .collect(), + tree_oid: Some(self.oid_to_pb(tree_hex)), + author: author_sig.as_ref().map(gix_sig_to_pb), + committer: committer_sig.as_ref().map(gix_sig_to_pb), + subject, + body, + message, + trailers: Vec::new(), + signature: None, + stats: None, + authored_at: author_sig.as_ref().map(|s| prost_types::Timestamp { + seconds: s.seconds(), + nanos: 0, + }), + committed_at: committer_sig.as_ref().map(|s| prost_types::Timestamp { + seconds: s.seconds(), + nanos: 0, + }), + raw: if request.include_raw { + commit.data.clone() + } else { + Vec::new() + }, + }) + } +} + +pub(crate) fn gix_sig_to_pb(sig: &gix::actor::SignatureRef<'_>) -> crate::pb::Signature { + let time = sig.time().ok(); + crate::pb::Signature { + identity: Some(crate::pb::Identity { + name: sig.name.to_string(), + email: sig.email.to_string(), + }), + when: Some(prost_types::Timestamp { + seconds: sig.seconds(), + nanos: 0, + }), + timezone_offset: time.map(|t| t.offset / 60).unwrap_or(0), + } +} diff --git a/commit/get_commit_ancestors.rs b/commit/get_commit_ancestors.rs new file mode 100644 index 0000000..d6cc1c1 --- /dev/null +++ b/commit/get_commit_ancestors.rs @@ -0,0 +1,28 @@ +use crate::bare::GitBare; +use crate::error::GitResult; +use crate::pb::{GetCommitAncestorsRequest, GetCommitAncestorsResponse, ListCommitsRequest}; + +impl GitBare { + pub fn get_commit_ancestors( + &self, + request: GetCommitAncestorsRequest, + ) -> GitResult { + let response = self.list_commits(ListCommitsRequest { + repository: request.repository, + revision: request.revision, + path: String::new(), + since: None, + until: None, + first_parent: request.first_parent, + all: false, + reverse: false, + max_parents: 0, + min_parents: 0, + pagination: request.pagination, + })?; + Ok(GetCommitAncestorsResponse { + commits: response.commits, + page_info: response.page_info, + }) + } +} diff --git a/commit/list_commits.rs b/commit/list_commits.rs new file mode 100644 index 0000000..90b18ab --- /dev/null +++ b/commit/list_commits.rs @@ -0,0 +1,86 @@ +use crate::bare::GitBare; +use crate::error::{GitError, GitResult}; +use crate::paginate; +use crate::pb::{GetCommitRequest, ListCommitsRequest, ListCommitsResponse, object_selector}; + +impl GitBare { + pub fn list_commits(&self, request: ListCommitsRequest) -> GitResult { + let revision = match request.revision.clone().and_then(|s| s.selector) { + Some(object_selector::Selector::Oid(oid)) => oid.hex, + Some(object_selector::Selector::Revision(name)) => name.revision, + None => "HEAD".into(), + }; + + let mut args = vec![ + "--git-dir".to_string(), + self.bare_dir.to_string_lossy().into_owned(), + "rev-list".into(), + ]; + if request.first_parent { + args.push("--first-parent".into()); + } + if request.reverse { + args.push("--reverse".into()); + } + if request.max_parents > 0 { + args.push(format!("--max-parents={}", request.max_parents)); + } + if request.min_parents > 0 { + args.push(format!("--min-parents={}", request.min_parents)); + } + if let Some(since) = request.since.as_ref() { + args.push(format!("--since=@{}", since.seconds)); + } + if let Some(until) = request.until.as_ref() { + args.push(format!("--until=@{}", until.seconds)); + } + if request.all { + args.push("--all".into()); + } else { + args.push(revision); + } + if !request.path.is_empty() { + args.push("--".into()); + args.push(request.path.clone()); + } + + let result = duct::cmd("git", &args) + .stdout_capture() + .stderr_capture() + .unchecked() + .run()?; + if !result.status.success() { + return Err(GitError::CommandFailed { + status_code: result.status.code(), + stderr: String::from_utf8_lossy(&result.stderr).into_owned(), + }); + } + + let ids = String::from_utf8_lossy(&result.stdout) + .lines() + .map(str::trim) + .filter(|line| !line.is_empty()) + .map(ToOwned::to_owned) + .collect::>(); + let (page_ids, page_info) = paginate::paginate(&ids, request.pagination.as_ref()); + + let mut commits = Vec::with_capacity(page_ids.len()); + for id in page_ids { + commits.push(self.get_commit(GetCommitRequest { + repository: request.repository.clone(), + revision: Some(crate::pb::ObjectSelector { + selector: Some(object_selector::Selector::Revision(crate::pb::ObjectName { + revision: id, + })), + }), + include_stats: false, + include_raw: false, + })?); + } + + Ok(ListCommitsResponse { + commits, + page_info: Some(page_info), + }) + } +} diff --git a/commit/mod.rs b/commit/mod.rs new file mode 100644 index 0000000..67c33bf --- /dev/null +++ b/commit/mod.rs @@ -0,0 +1,7 @@ +pub mod cherry_pick_commit; +pub mod compare_commits; +pub mod create_commit; +pub mod get_commit; +pub mod get_commit_ancestors; +pub mod list_commits; +pub mod revert_commit; diff --git a/commit/revert_commit.rs b/commit/revert_commit.rs new file mode 100644 index 0000000..0a20d84 --- /dev/null +++ b/commit/revert_commit.rs @@ -0,0 +1,146 @@ +use crate::bare::GitBare; +use crate::commit::create_commit::command_ok; +use crate::error::{GitError, GitResult}; +use crate::pb::{CreateCommitResponse, GetCommitRequest, RevertCommitRequest}; + +impl GitBare { + pub fn revert_commit(&self, request: RevertCommitRequest) -> GitResult { + let target_branch = request.branch.clone(); + let revert_revision = match request.commit.and_then(|s| s.selector) { + Some(crate::pb::object_selector::Selector::Oid(oid)) => oid.hex, + Some(crate::pb::object_selector::Selector::Revision(name)) => name.revision, + None => return Err(GitError::InvalidArgument("commit is required".into())), + }; + + let repo = self.gix_repo()?; + + let branch_ref = format!("refs/heads/{}", target_branch); + let branch_tip = repo + .find_reference(branch_ref.as_str()) + .ok() + .and_then(|mut r| r.peel_to_id().ok()) + .map(|id| id.to_string()) + .ok_or_else(|| GitError::RefNotFound(target_branch.clone()))?; + + let revert_id = repo.rev_parse_single(revert_revision.as_str())?; + let revert_obj = revert_id + .object()? + .try_into_commit() + .map_err(|e| GitError::Gix(e.to_string()))?; + + let parent_ids: Vec = revert_obj.parent_ids().map(|p| p.to_string()).collect(); + if parent_ids.len() > 1 { + return Err(GitError::InvalidArgument( + "reverting merge commits is not supported without mainline".into(), + )); + } + let parent_hex = parent_ids + .first() + .ok_or_else(|| GitError::InvalidArgument("cannot revert root commit".into()))?; + + let tmp_index = tempfile::Builder::new() + .prefix("gitks-revert-") + .tempfile_in(&self.bare_dir)?; + let idx_path = tmp_index.path().to_string_lossy().into_owned(); + let bare = self.bare_dir.to_string_lossy().into_owned(); + + let read_tree = duct::cmd( + "git", + ["--git-dir", bare.as_str(), "read-tree", branch_tip.as_str()], + ) + .env("GIT_INDEX_FILE", &idx_path) + .stdout_capture() + .stderr_capture() + .unchecked() + .run()?; + command_ok(read_tree)?; + + let diff = duct::cmd( + "git", + [ + "--git-dir", + bare.as_str(), + "diff", + "--binary", + "--full-index", + revert_revision.as_str(), + parent_hex.as_str(), + ], + ) + .stdout_capture() + .stderr_capture() + .unchecked() + .run()?; + let patch_data = command_ok(diff)?; + + let apply = duct::cmd( + "git", + [ + "--git-dir", + bare.as_str(), + "apply", + "--cached", + "--allow-empty", + "-", + ], + ) + .env("GIT_INDEX_FILE", &idx_path) + .stdin_bytes(patch_data.as_bytes()) + .stdout_capture() + .stderr_capture() + .unchecked() + .run()?; + if !apply.status.success() { + return Err(GitError::Internal(format!( + "revert apply failed: {}", + String::from_utf8_lossy(&apply.stderr) + ))); + } + + let write_tree = duct::cmd("git", ["--git-dir", bare.as_str(), "write-tree"]) + .env("GIT_INDEX_FILE", &idx_path) + .stdout_capture() + .stderr_capture() + .unchecked() + .run()?; + let tree_id = command_ok(write_tree)?.trim().to_string(); + + let subject = revert_obj + .message_raw()? + .to_string() + .lines() + .next() + .unwrap_or_default() + .to_string(); + let message = format!( + "Revert \"{}\"\n\nThis reverts commit {}.", + subject, revert_revision + ); + + let commit_id = self.commit_tree( + &tree_id, + std::slice::from_ref(&branch_tip), + &message, + request.committer.as_ref(), + request.committer.as_ref(), + )?; + + self.update_branch_ref(&target_branch, &commit_id, Some(&branch_tip), false)?; + + Ok(CreateCommitResponse { + commit: Some(self.get_commit(GetCommitRequest { + repository: request.repository, + revision: Some(crate::pb::ObjectSelector { + selector: Some(crate::pb::object_selector::Selector::Revision( + crate::pb::ObjectName { + revision: commit_id, + }, + )), + }), + include_stats: false, + include_raw: false, + })?), + branch: target_branch, + }) + } +} diff --git a/commit/types.rs b/commit/types.rs new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/commit/types.rs @@ -0,0 +1 @@ + diff --git a/diff/get_commit_diff.rs b/diff/get_commit_diff.rs new file mode 100644 index 0000000..88841f3 --- /dev/null +++ b/diff/get_commit_diff.rs @@ -0,0 +1,86 @@ +use crate::bare::GitBare; +use crate::error::{GitError, GitResult}; +use crate::pb::{GetCommitDiffRequest, GetDiffRequest, GetDiffResponse}; + +impl GitBare { + pub fn get_commit_diff(&self, request: GetCommitDiffRequest) -> GitResult { + let commit = match request.commit.and_then(|s| s.selector) { + Some(crate::pb::object_selector::Selector::Oid(oid)) => oid.hex, + Some(crate::pb::object_selector::Selector::Revision(name)) => name.revision, + None => "HEAD".into(), + }; + let base = self.first_parent_or_empty_tree(&commit)?; + self.get_diff(GetDiffRequest { + repository: request.repository, + base: Some(crate::pb::ObjectSelector { + selector: Some(crate::pb::object_selector::Selector::Revision( + crate::pb::ObjectName { revision: base }, + )), + }), + head: Some(crate::pb::ObjectSelector { + selector: Some(crate::pb::object_selector::Selector::Revision( + crate::pb::ObjectName { revision: commit }, + )), + }), + options: request.options, + pagination: request.pagination, + }) + } + + fn first_parent_or_empty_tree(&self, commit: &str) -> GitResult { + let result = duct::cmd( + "git", + [ + "--git-dir", + self.bare_dir.to_string_lossy().as_ref(), + "rev-list", + "--parents", + "-n", + "1", + commit, + ], + ) + .stdout_capture() + .stderr_capture() + .unchecked() + .run()?; + if !result.status.success() { + return Err(GitError::CommandFailed { + status_code: result.status.code(), + stderr: String::from_utf8_lossy(&result.stderr).into_owned(), + }); + } + let output = String::from_utf8_lossy(&result.stdout); + let parts = output.split_whitespace().collect::>(); + if let Some(parent) = parts.get(1) { + return Ok((*parent).to_string()); + } + + let empty_tree = duct::cmd( + "git", + [ + "--git-dir", + self.bare_dir.to_string_lossy().as_ref(), + "hash-object", + "-t", + "tree", + "-w", + "--stdin", + ], + ) + .stdin_bytes(Vec::::new()) + .stdout_capture() + .stderr_capture() + .unchecked() + .run()?; + if !empty_tree.status.success() { + return Err(GitError::CommandFailed { + status_code: empty_tree.status.code(), + stderr: String::from_utf8_lossy(&empty_tree.stderr).into_owned(), + }); + } + Ok(String::from_utf8_lossy(&empty_tree.stdout) + .trim() + .to_string()) + } +} diff --git a/diff/get_diff.rs b/diff/get_diff.rs new file mode 100644 index 0000000..47843c9 --- /dev/null +++ b/diff/get_diff.rs @@ -0,0 +1,330 @@ +use crate::bare::GitBare; +use crate::diff::get_diff_stats::{diff_stats_for_range, push_diff_options}; +use crate::error::{GitError, GitResult}; +use crate::paginate; +use crate::pb::diff_file::ChangeType; +use crate::pb::{DiffFile, GetDiffRequest, GetDiffResponse}; + +#[derive(Debug, Clone)] +struct NameStatusEntry { + status: char, + old_path: String, + new_path: String, + similarity: f64, +} + +#[derive(Debug, Clone, Default)] +struct TreeMeta { + oid_hex: String, + mode: u32, +} + +impl GitBare { + pub fn get_diff(&self, request: GetDiffRequest) -> GitResult { + let base = match request.base.and_then(|s| s.selector) { + Some(crate::pb::object_selector::Selector::Oid(oid)) => oid.hex, + Some(crate::pb::object_selector::Selector::Revision(name)) => name.revision, + None => "HEAD".into(), + }; + let head = match request.head.and_then(|s| s.selector) { + Some(crate::pb::object_selector::Selector::Oid(oid)) => oid.hex, + Some(crate::pb::object_selector::Selector::Revision(name)) => name.revision, + None => "HEAD".into(), + }; + + let options = request.options.as_ref(); + let entries = self.diff_name_status(&base, &head, options)?; + let max_files = options.and_then(|o| (o.max_files > 0).then_some(o.max_files as usize)); + let overflow = max_files.is_some_and(|max| entries.len() > max); + let entries_to_build = + max_files.map_or(entries.as_slice(), |max| &entries[..entries.len().min(max)]); + + let mut files = Vec::with_capacity(entries_to_build.len()); + for entry in entries_to_build { + let old_meta = if !entry.old_path.is_empty() { + self.tree_meta(&base, &entry.old_path).ok().flatten() + } else { + None + }; + let new_meta = if !entry.new_path.is_empty() { + self.tree_meta(&head, &entry.new_path).ok().flatten() + } else { + None + }; + let (additions, deletions, binary) = self.path_numstat(&base, &head, entry)?; + let (patch, too_large) = self.path_patch(&base, &head, entry, options)?; + + files.push(DiffFile { + old_path: entry.old_path.clone(), + new_path: entry.new_path.clone(), + old_oid: old_meta.as_ref().map(|m| self.oid_to_pb(&m.oid_hex)), + new_oid: new_meta.as_ref().map(|m| self.oid_to_pb(&m.oid_hex)), + old_mode: old_meta.as_ref().map(|m| m.mode).unwrap_or(0), + new_mode: new_meta.as_ref().map(|m| m.mode).unwrap_or(0), + change_type: change_type(entry.status) as i32, + binary, + too_large, + additions, + deletions, + hunks: Vec::new(), + patch, + similarity: entry.similarity, + }); + } + + let stats = diff_stats_for_range(self, &base, &head, options)?; + let (files, page_info) = paginate::paginate(&files, request.pagination.as_ref()); + + Ok(GetDiffResponse { + files, + stats: Some(stats), + page_info: Some(page_info), + overflow, + }) + } + + fn diff_name_status( + &self, + base: &str, + head: &str, + options: Option<&crate::pb::DiffOptions>, + ) -> GitResult> { + let mut args = vec![ + "--git-dir".to_string(), + self.bare_dir.to_string_lossy().into_owned(), + "diff".into(), + "--name-status".into(), + "-z".into(), + ]; + push_diff_options(&mut args, options); + args.push(base.to_string()); + args.push(head.to_string()); + if let Some(options) = options + && !options.pathspec.is_empty() + { + args.push("--".into()); + args.extend(options.pathspec.iter().cloned()); + } + + let result = duct::cmd("git", &args) + .stdout_capture() + .stderr_capture() + .unchecked() + .run()?; + if !result.status.success() { + return Err(GitError::CommandFailed { + status_code: result.status.code(), + stderr: String::from_utf8_lossy(&result.stderr).into_owned(), + }); + } + + let parts = result + .stdout + .split(|b| *b == 0) + .filter(|part| !part.is_empty()) + .map(|part| String::from_utf8_lossy(part).into_owned()) + .collect::>(); + + let mut entries = Vec::new(); + let mut idx = 0; + while idx < parts.len() { + let status_token = &parts[idx]; + idx += 1; + let status = status_token.chars().next().unwrap_or('M'); + let similarity = status_token + .get(1..) + .and_then(|s| s.parse::().ok()) + .unwrap_or(0.0); + + if matches!(status, 'R' | 'C') { + if idx + 1 >= parts.len() { + break; + } + let old_path = parts[idx].clone(); + let new_path = parts[idx + 1].clone(); + idx += 2; + entries.push(NameStatusEntry { + status, + old_path, + new_path, + similarity, + }); + } else { + if idx >= parts.len() { + break; + } + let path = parts[idx].clone(); + idx += 1; + let (old_path, new_path) = match status { + 'A' => (String::new(), path), + 'D' => (path, String::new()), + _ => (path.clone(), path), + }; + entries.push(NameStatusEntry { + status, + old_path, + new_path, + similarity, + }); + } + } + + Ok(entries) + } + + fn tree_meta(&self, revision: &str, path: &str) -> GitResult> { + let result = duct::cmd( + "git", + [ + "--git-dir", + self.bare_dir.to_string_lossy().as_ref(), + "ls-tree", + "-z", + "-l", + revision, + "--", + path, + ], + ) + .stdout_capture() + .stderr_capture() + .unchecked() + .run()?; + if !result.status.success() || result.stdout.is_empty() { + return Ok(None); + } + let record = result + .stdout + .split(|b| *b == 0) + .find(|part| !part.is_empty()) + .map(|part| String::from_utf8_lossy(part).into_owned()); + let Some(record) = record else { + return Ok(None); + }; + let Some((meta, _path)) = record.split_once('\t') else { + return Ok(None); + }; + let parts = meta.split_whitespace().collect::>(); + if parts.len() < 3 { + return Ok(None); + } + Ok(Some(TreeMeta { + mode: u32::from_str_radix(parts[0], 8).unwrap_or(0), + oid_hex: parts[2].to_string(), + })) + } + + fn path_numstat( + &self, + base: &str, + head: &str, + entry: &NameStatusEntry, + ) -> GitResult<(u32, u32, bool)> { + let path = if entry.new_path.is_empty() { + &entry.old_path + } else { + &entry.new_path + }; + let result = duct::cmd( + "git", + [ + "--git-dir", + self.bare_dir.to_string_lossy().as_ref(), + "diff", + "--numstat", + base, + head, + "--", + path, + ], + ) + .stdout_capture() + .stderr_capture() + .unchecked() + .run()?; + if !result.status.success() { + return Err(GitError::CommandFailed { + status_code: result.status.code(), + stderr: String::from_utf8_lossy(&result.stderr).into_owned(), + }); + } + let line = String::from_utf8_lossy(&result.stdout) + .lines() + .next() + .unwrap_or_default() + .to_string(); + let mut parts = line.split('\t'); + let add = parts.next().unwrap_or_default(); + let del = parts.next().unwrap_or_default(); + let binary = add == "-" || del == "-"; + Ok((add.parse().unwrap_or(0), del.parse().unwrap_or(0), binary)) + } + + fn path_patch( + &self, + base: &str, + head: &str, + entry: &NameStatusEntry, + options: Option<&crate::pb::DiffOptions>, + ) -> GitResult<(Vec, bool)> { + let Some(options) = options else { + return Ok((Vec::new(), false)); + }; + if !options.include_patch { + return Ok((Vec::new(), false)); + } + + let path = if entry.new_path.is_empty() { + &entry.old_path + } else { + &entry.new_path + }; + let context = options.context_lines.to_string(); + let mut args = vec![ + "--git-dir".to_string(), + self.bare_dir.to_string_lossy().into_owned(), + "diff".into(), + "--patch".into(), + format!("--unified={context}"), + ]; + if options.include_binary { + args.push("--binary".into()); + } + push_diff_options(&mut args, Some(options)); + args.push(base.to_string()); + args.push(head.to_string()); + args.push("--".into()); + args.push(path.to_string()); + + let result = duct::cmd("git", &args) + .stdout_capture() + .stderr_capture() + .unchecked() + .run()?; + if !result.status.success() { + return Err(GitError::CommandFailed { + status_code: result.status.code(), + stderr: String::from_utf8_lossy(&result.stderr).into_owned(), + }); + } + + let mut patch = result.stdout; + let too_large = options.max_bytes > 0 && patch.len() > options.max_bytes as usize; + if too_large { + patch.truncate(options.max_bytes as usize); + } + Ok((patch, too_large)) + } +} + +fn change_type(status: char) -> ChangeType { + match status { + 'A' => ChangeType::DiffFileChangeTypeAdded, + 'D' => ChangeType::DiffFileChangeTypeDeleted, + 'R' => ChangeType::DiffFileChangeTypeRenamed, + 'C' => ChangeType::DiffFileChangeTypeCopied, + 'T' => ChangeType::DiffFileChangeTypeTypeChanged, + 'U' => ChangeType::DiffFileChangeTypeUnmerged, + _ => ChangeType::DiffFileChangeTypeModified, + } +} diff --git a/diff/get_diff_stats.rs b/diff/get_diff_stats.rs new file mode 100644 index 0000000..03a0e16 --- /dev/null +++ b/diff/get_diff_stats.rs @@ -0,0 +1,108 @@ +use crate::bare::GitBare; +use crate::error::{GitError, GitResult}; +use crate::pb::GetDiffStatsRequest; + +impl GitBare { + pub fn get_diff_stats(&self, request: GetDiffStatsRequest) -> GitResult { + let base = match request.base.and_then(|s| s.selector) { + Some(crate::pb::object_selector::Selector::Oid(oid)) => oid.hex, + Some(crate::pb::object_selector::Selector::Revision(name)) => name.revision, + None => "HEAD".into(), + }; + let head = match request.head.and_then(|s| s.selector) { + Some(crate::pb::object_selector::Selector::Oid(oid)) => oid.hex, + Some(crate::pb::object_selector::Selector::Revision(name)) => name.revision, + None => "HEAD".into(), + }; + diff_stats_for_range(self, &base, &head, request.options.as_ref()) + } +} + +pub(crate) fn diff_stats_for_range( + repo: &GitBare, + base: &str, + head: &str, + options: Option<&crate::pb::DiffOptions>, +) -> GitResult { + let mut args = vec![ + "--git-dir".to_string(), + repo.bare_dir.to_string_lossy().into_owned(), + "diff".into(), + "--shortstat".into(), + ]; + push_diff_options(&mut args, options); + args.push(base.to_string()); + args.push(head.to_string()); + if let Some(options) = options + && !options.pathspec.is_empty() + { + args.push("--".into()); + args.extend(options.pathspec.iter().cloned()); + } + + let result = duct::cmd("git", &args) + .stdout_capture() + .stderr_capture() + .unchecked() + .run()?; + if !result.status.success() { + return Err(GitError::CommandFailed { + status_code: result.status.code(), + stderr: String::from_utf8_lossy(&result.stderr).into_owned(), + }); + } + + Ok(parse_shortstat(&String::from_utf8_lossy(&result.stdout))) +} + +pub(crate) fn parse_shortstat(output: &str) -> crate::pb::DiffStats { + let mut stats = crate::pb::DiffStats::default(); + for part in output.trim().split(',') { + let part = part.trim(); + if let Some(n) = part + .strip_suffix(" insertion(+)") + .or_else(|| part.strip_suffix(" insertions(+)")) + { + stats.additions = n.trim().parse().unwrap_or(0); + } else if let Some(n) = part + .strip_suffix(" deletion(-)") + .or_else(|| part.strip_suffix(" deletions(-)")) + { + stats.deletions = n.trim().parse().unwrap_or(0); + } else if let Some(n) = part + .strip_suffix(" file changed") + .or_else(|| part.strip_suffix(" files changed")) + { + stats.changed_files = n.trim().parse().unwrap_or(0); + } + } + stats +} + +pub(crate) fn push_diff_options(args: &mut Vec, options: Option<&crate::pb::DiffOptions>) { + let Some(options) = options else { + return; + }; + + if options.rename_detection { + args.push("-M".into()); + } + if options.copy_detection { + args.push("-C".into()); + } + + match crate::pb::diff_options::WhitespaceMode::try_from(options.whitespace_mode) + .unwrap_or(crate::pb::diff_options::WhitespaceMode::DiffWhitespaceModeDefault) + { + crate::pb::diff_options::WhitespaceMode::DiffWhitespaceModeIgnoreAll => { + args.push("-w".into()) + } + crate::pb::diff_options::WhitespaceMode::DiffWhitespaceModeIgnoreChange => { + args.push("-b".into()) + } + crate::pb::diff_options::WhitespaceMode::DiffWhitespaceModeIgnoreEol => { + args.push("--ignore-space-at-eol".into()); + } + _ => {} + } +} diff --git a/diff/get_patch.rs b/diff/get_patch.rs new file mode 100644 index 0000000..d917106 --- /dev/null +++ b/diff/get_patch.rs @@ -0,0 +1,42 @@ +use crate::bare::GitBare; +use crate::error::{GitError, GitResult}; +use crate::pb::{GetPatchRequest, GetPatchResponse}; + +impl GitBare { + pub fn get_patch(&self, request: GetPatchRequest) -> GitResult> { + let base = match request.base.and_then(|s| s.selector) { + Some(crate::pb::object_selector::Selector::Oid(oid)) => oid.hex, + Some(crate::pb::object_selector::Selector::Revision(name)) => name.revision, + None => "HEAD".into(), + }; + let head = match request.head.and_then(|s| s.selector) { + Some(crate::pb::object_selector::Selector::Oid(oid)) => oid.hex, + Some(crate::pb::object_selector::Selector::Revision(name)) => name.revision, + None => "HEAD".into(), + }; + let result = duct::cmd( + "git", + [ + "--git-dir", + self.bare_dir.to_string_lossy().as_ref(), + "diff", + "--patch", + &base, + &head, + ], + ) + .stdout_capture() + .stderr_capture() + .unchecked() + .run()?; + if !result.status.success() { + return Err(GitError::CommandFailed { + status_code: result.status.code(), + stderr: String::from_utf8_lossy(&result.stderr).into_owned(), + }); + } + Ok(vec![GetPatchResponse { + data: result.stdout, + }]) + } +} diff --git a/diff/mod.rs b/diff/mod.rs new file mode 100644 index 0000000..623e8fa --- /dev/null +++ b/diff/mod.rs @@ -0,0 +1,4 @@ +pub mod get_commit_diff; +pub mod get_diff; +pub mod get_diff_stats; +pub mod get_patch; diff --git a/error.rs b/error.rs new file mode 100644 index 0000000..4dc72d9 --- /dev/null +++ b/error.rs @@ -0,0 +1,78 @@ +pub type GitResult = Result; + +#[derive(Debug, thiserror::Error)] +pub enum GitError { + #[error("repository is not bare")] + NotBareRepository, + #[error("git command failed with status {status_code:?}: {stderr}")] + CommandFailed { + status_code: Option, + stderr: String, + }, + #[error("unsafe git command rejected: {0}")] + UnsafeCommand(String), + #[error("object not found: {0}")] + ObjectNotFound(String), + #[error("reference not found: {0}")] + RefNotFound(String), + #[error("parse error: {0}")] + ParseError(String), + #[error(transparent)] + Io(#[from] std::io::Error), + #[error("gix error: {0}")] + Gix(String), + #[error("repository not found")] + RepoNotFound, + #[error("internal error: {0}")] + Internal(String), + #[error("not found: {0}")] + NotFound(String), + #[error("invalid oid: {0}")] + InvalidOid(String), + #[error("locked: {0}")] + Locked(String), + #[error("permission denied: {0}")] + PermissionDenied(String), + #[error("authentication failed: {0}")] + AuthFailed(String), + #[error("payload too large: {0}")] + PayloadTooLarge(String), + #[error("invalid argument: {0}")] + InvalidArgument(String), +} + +macro_rules! impl_gix_error { + ($err_type:path) => { + impl From<$err_type> for GitError { + fn from(e: $err_type) -> Self { + GitError::Gix(e.to_string()) + } + } + }; +} + +impl_gix_error!(gix::object::find::existing::Error); +impl_gix_error!(gix::object::find::existing::with_conversion::Error); +impl_gix_error!(gix::object::find::Error); +impl_gix_error!(gix::reference::iter::Error); +impl_gix_error!(gix::reference::iter::init::Error); +impl_gix_error!(gix::reference::find::existing::Error); +impl_gix_error!(gix::reference::find::Error); +impl_gix_error!(gix::reference::head_id::Error); +impl_gix_error!(gix::repository::merge_bases_many::Error); +impl_gix_error!(gix::reference::peel::Error); +impl_gix_error!(gix::repository::blame_file::Error); +impl_gix_error!(gix::blame::Error); +impl_gix_error!(gix::revision::walk::Error); +impl_gix_error!(gix::revision::walk::iter::Error); +impl_gix_error!(gix::revision::spec::parse::single::Error); +impl_gix_error!(gix::open::Error); +impl_gix_error!(gix::objs::decode::Error); +impl_gix_error!(gix::date::Error); +impl_gix_error!(gix::repository::diff_tree_to_tree::Error); + +impl From> for GitError { + fn from(e: Box) -> Self { + GitError::Gix(e.to_string()) + } +} diff --git a/init.rs b/init.rs new file mode 100644 index 0000000..76f928e --- /dev/null +++ b/init.rs @@ -0,0 +1,24 @@ +use crate::bare::GitBare; +use crate::error::{GitError, GitResult}; + +impl GitBare { + pub fn init_repository(&self, bare: bool) -> GitResult<()> { + let mut args = vec!["init".to_string()]; + if bare { + args.push("--bare".into()); + } + args.push(self.bare_dir.to_string_lossy().into_owned()); + let result = duct::cmd("git", &args) + .stdout_capture() + .stderr_capture() + .unchecked() + .run()?; + if !result.status.success() { + return Err(GitError::CommandFailed { + status_code: result.status.code(), + stderr: String::from_utf8_lossy(&result.stderr).into_owned(), + }); + } + Ok(()) + } +} diff --git a/lib.rs b/lib.rs new file mode 100644 index 0000000..e927730 --- /dev/null +++ b/lib.rs @@ -0,0 +1,17 @@ +pub mod archive; +pub mod bare; +pub mod blame; +pub mod blob; +pub mod branch; +pub mod commit; +pub mod diff; +pub mod error; +pub mod init; +pub mod merge; +pub mod oid; +pub mod pack; +pub mod paginate; +pub mod pb; +pub mod refs; +pub mod tag; +pub mod tree; diff --git a/merge/check_merge.rs b/merge/check_merge.rs new file mode 100644 index 0000000..b613c7d --- /dev/null +++ b/merge/check_merge.rs @@ -0,0 +1,111 @@ +use crate::bare::GitBare; +use crate::error::GitResult; +use crate::pb::{CheckMergeRequest, MergeResult, merge_result}; + +impl GitBare { + pub fn check_merge(&self, request: CheckMergeRequest) -> GitResult { + let target = match request.target.and_then(|s| s.selector) { + Some(crate::pb::object_selector::Selector::Oid(oid)) => oid.hex, + Some(crate::pb::object_selector::Selector::Revision(name)) => name.revision, + None => "HEAD".into(), + }; + let source = match request.source.and_then(|s| s.selector) { + Some(crate::pb::object_selector::Selector::Oid(oid)) => oid.hex, + Some(crate::pb::object_selector::Selector::Revision(name)) => name.revision, + None => "HEAD".into(), + }; + + let repo = self.gix_repo()?; + let target_id = repo.rev_parse_single(target.as_str())?; + let source_id = repo.rev_parse_single(source.as_str())?; + + if target_id == source_id { + return Ok(MergeResult { + status: merge_result::Status::MergeResultStatusAlreadyUpToDate as i32, + commit: None, + merge_base: Some(self.oid_to_pb(target_id.to_string())), + conflicts: vec![], + stats: None, + message: String::from("Already up to date"), + }); + } + + let merge_base = repo + .merge_base(target_id.detach(), source_id.detach()) + .ok() + .map(|id| id.to_string()); + + if let Some(ref base) = merge_base { + if *base == target_id.to_string() { + return Ok(MergeResult { + status: merge_result::Status::MergeResultStatusFastForward as i32, + commit: None, + merge_base: Some(self.oid_to_pb(base.clone())), + conflicts: vec![], + stats: None, + message: String::from("Fast-forward"), + }); + } + if *base == source_id.to_string() { + return Ok(MergeResult { + status: merge_result::Status::MergeResultStatusAlreadyUpToDate as i32, + commit: None, + merge_base: Some(self.oid_to_pb(base.clone())), + conflicts: vec![], + stats: None, + message: String::from("Already up to date"), + }); + } + } + + let merge_base_oid = merge_base.as_ref().map(|b| self.oid_to_pb(b.clone())); + + let result = duct::cmd( + "git", + [ + "--git-dir", + self.bare_dir.to_string_lossy().as_ref(), + "merge-tree", + "--write-tree", + "--no-messages", + "-z", + &target, + &source, + ], + ) + .stdout_capture() + .stderr_capture() + .unchecked() + .run()?; + + let stdout = String::from_utf8_lossy(&result.stdout).into_owned(); + + if result.status.success() { + Ok(MergeResult { + status: merge_result::Status::MergeResultStatusMerged as i32, + commit: None, + merge_base: merge_base_oid, + conflicts: vec![], + stats: None, + message: stdout.trim().to_string(), + }) + } else { + let conflicts = stdout + .split('\0') + .filter(|s| !s.is_empty()) + .map(|path| crate::pb::MergeConflict { + path: path.to_string(), + ..Default::default() + }) + .collect(); + Ok(MergeResult { + status: merge_result::Status::MergeResultStatusConflicts as i32, + commit: None, + merge_base: merge_base_oid, + conflicts, + stats: None, + message: String::from("Merge conflicts detected"), + }) + } + } +} diff --git a/merge/do_merge.rs b/merge/do_merge.rs new file mode 100644 index 0000000..d10ef71 --- /dev/null +++ b/merge/do_merge.rs @@ -0,0 +1,173 @@ +use crate::bare::GitBare; +use crate::error::{GitError, GitResult}; +use crate::pb::{MergeRequest, MergeResult, merge_result}; + +impl GitBare { + pub fn merge(&self, request: MergeRequest) -> GitResult { + let target_branch = request.target_branch.clone(); + let source_revision = match request.source.and_then(|s| s.selector) { + Some(crate::pb::object_selector::Selector::Oid(oid)) => oid.hex, + Some(crate::pb::object_selector::Selector::Revision(name)) => name.revision, + None => return Err(GitError::InvalidArgument("source is required".into())), + }; + + let repo = self.gix_repo()?; + + let branch_ref = format!("refs/heads/{}", target_branch); + let target_id = repo + .find_reference(branch_ref.as_str()) + .ok() + .and_then(|mut r| r.peel_to_id().ok()) + .map(|id| id.to_string()) + .ok_or_else(|| GitError::RefNotFound(target_branch.clone()))?; + + let source_id = repo.rev_parse_single(source_revision.as_str())?.to_string(); + + if target_id == source_id { + return Ok(MergeResult { + status: merge_result::Status::MergeResultStatusAlreadyUpToDate as i32, + commit: None, + merge_base: Some(self.oid_to_pb(target_id)), + conflicts: vec![], + stats: None, + message: String::from("Already up to date"), + }); + } + + let target_oid = gix::hash::ObjectId::from_hex(target_id.as_bytes()) + .map_err(|e| GitError::InvalidOid(e.to_string()))?; + let source_oid = gix::hash::ObjectId::from_hex(source_id.as_bytes()) + .map_err(|e| GitError::InvalidOid(e.to_string()))?; + + let merge_base = repo + .merge_base(target_oid, source_oid) + .ok() + .map(|id| id.to_string()); + + let ff_mode = request + .options + .as_ref() + .map(|o| o.fast_forward) + .unwrap_or(0); + let ff_only = + ff_mode == crate::pb::merge_options::FastForwardMode::MergeFastForwardModeOnly as i32; + + if let Some(ref base) = merge_base { + if *base == target_id { + let no_ff = ff_mode + == crate::pb::merge_options::FastForwardMode::MergeFastForwardModeNoFf as i32; + + if !no_ff { + self.update_branch_ref(&target_branch, &source_id, Some(&target_id), false)?; + return Ok(MergeResult { + status: merge_result::Status::MergeResultStatusFastForward as i32, + commit: None, + merge_base: Some(self.oid_to_pb(base.clone())), + conflicts: vec![], + stats: None, + message: format!("Fast-forward to {}", source_id), + }); + } + } + if *base == source_id { + return Ok(MergeResult { + status: merge_result::Status::MergeResultStatusAlreadyUpToDate as i32, + commit: None, + merge_base: Some(self.oid_to_pb(base.clone())), + conflicts: vec![], + stats: None, + message: String::from("Already up to date"), + }); + } + } + + if ff_only { + return Ok(MergeResult { + status: merge_result::Status::MergeResultStatusAborted as i32, + commit: None, + merge_base: merge_base.map(|b| self.oid_to_pb(b)), + conflicts: vec![], + stats: None, + message: String::from("Not possible to fast-forward"), + }); + } + + let bare = self.bare_dir.to_string_lossy().into_owned(); + let result = duct::cmd( + "git", + [ + "--git-dir", + bare.as_str(), + "merge-tree", + "--write-tree", + "--no-messages", + "-z", + target_id.as_str(), + source_id.as_str(), + ], + ) + .stdout_capture() + .stderr_capture() + .unchecked() + .run()?; + + let stdout = String::from_utf8_lossy(&result.stdout).into_owned(); + + if result.status.success() { + let merged_tree = stdout.trim().to_string(); + let message = if !request.message.is_empty() { + request.message.clone() + } else { + format!("Merge '{}' into {}", source_revision, target_branch) + }; + + let parents = vec![target_id.clone(), source_id.clone()]; + let commit_id = self.commit_tree( + &merged_tree, + &parents, + &message, + request.committer.as_ref(), + request.committer.as_ref(), + )?; + + self.update_branch_ref(&target_branch, &commit_id, Some(&target_id), false)?; + + Ok(MergeResult { + status: merge_result::Status::MergeResultStatusMerged as i32, + commit: Some(self.get_commit(crate::pb::GetCommitRequest { + repository: request.repository, + revision: Some(crate::pb::ObjectSelector { + selector: Some(crate::pb::object_selector::Selector::Revision( + crate::pb::ObjectName { + revision: commit_id, + }, + )), + }), + include_stats: false, + include_raw: false, + })?), + merge_base: merge_base.map(|b| self.oid_to_pb(b)), + conflicts: vec![], + stats: None, + message, + }) + } else { + let conflicts = stdout + .split('\0') + .filter(|s| !s.is_empty()) + .map(|path| crate::pb::MergeConflict { + path: path.to_string(), + ..Default::default() + }) + .collect(); + Ok(MergeResult { + status: merge_result::Status::MergeResultStatusConflicts as i32, + commit: None, + merge_base: merge_base.map(|b| self.oid_to_pb(b)), + conflicts, + stats: None, + message: String::from("Merge conflicts detected"), + }) + } + } +} diff --git a/merge/list_merge_conflicts.rs b/merge/list_merge_conflicts.rs new file mode 100644 index 0000000..304b197 --- /dev/null +++ b/merge/list_merge_conflicts.rs @@ -0,0 +1,69 @@ +use crate::bare::GitBare; +use crate::error::{GitError, GitResult}; +use crate::paginate; +use crate::pb::{ListMergeConflictsRequest, ListMergeConflictsResponse, MergeConflict}; + +impl GitBare { + pub fn list_merge_conflicts( + &self, + request: ListMergeConflictsRequest, + ) -> GitResult { + let target = match request.target.and_then(|s| s.selector) { + Some(crate::pb::object_selector::Selector::Oid(oid)) => oid.hex, + Some(crate::pb::object_selector::Selector::Revision(name)) => name.revision, + None => return Err(GitError::InvalidArgument("target is required".into())), + }; + let source = match request.source.and_then(|s| s.selector) { + Some(crate::pb::object_selector::Selector::Oid(oid)) => oid.hex, + Some(crate::pb::object_selector::Selector::Revision(name)) => name.revision, + None => return Err(GitError::InvalidArgument("source is required".into())), + }; + + let result = duct::cmd( + "git", + [ + "--git-dir", + self.bare_dir.to_string_lossy().as_ref(), + "merge-tree", + "--write-tree", + "--name-only", + "-z", + target.as_str(), + source.as_str(), + ], + ) + .stdout_capture() + .stderr_capture() + .unchecked() + .run()?; + + let stdout = String::from_utf8_lossy(&result.stdout); + + if result.status.success() { + return Ok(ListMergeConflictsResponse { + conflicts: vec![], + page_info: Some(crate::pb::PageInfo { + next_page_token: String::new(), + has_next_page: false, + total_count: 0, + }), + }); + } + + let mut conflicts: Vec = stdout + .split('\0') + .filter(|s| !s.is_empty()) + .map(|path| MergeConflict { + path: path.to_string(), + ..Default::default() + }) + .collect(); + + paginate::apply_sort(&mut conflicts, 0); + let (conflicts, page_info) = paginate::paginate(&conflicts, request.pagination.as_ref()); + Ok(ListMergeConflictsResponse { + conflicts, + page_info: Some(page_info), + }) + } +} diff --git a/merge/mod.rs b/merge/mod.rs new file mode 100644 index 0000000..921cf71 --- /dev/null +++ b/merge/mod.rs @@ -0,0 +1,5 @@ +pub mod check_merge; +pub mod do_merge; +pub mod list_merge_conflicts; +pub mod rebase; +pub mod resolve_merge_conflicts; diff --git a/merge/rebase.rs b/merge/rebase.rs new file mode 100644 index 0000000..9915146 --- /dev/null +++ b/merge/rebase.rs @@ -0,0 +1,192 @@ +use crate::bare::GitBare; +use crate::commit::create_commit::command_ok; +use crate::error::{GitError, GitResult}; +use crate::pb::{RebaseRequest, RebaseResult, rebase_result}; + +impl GitBare { + pub fn rebase(&self, request: RebaseRequest) -> GitResult { + let branch = request.branch.clone(); + let upstream_revision = match request.upstream.and_then(|s| s.selector) { + Some(crate::pb::object_selector::Selector::Oid(oid)) => oid.hex, + Some(crate::pb::object_selector::Selector::Revision(name)) => name.revision, + None => return Err(GitError::InvalidArgument("upstream is required".into())), + }; + + let repo = self.gix_repo()?; + let branch_ref = format!("refs/heads/{}", branch); + let branch_tip = repo + .find_reference(branch_ref.as_str()) + .ok() + .and_then(|mut r| r.peel_to_id().ok()) + .map(|id| id.to_string()) + .ok_or_else(|| GitError::RefNotFound(branch.clone()))?; + + let upstream_id = repo + .rev_parse_single(upstream_revision.as_str())? + .to_string(); + + if branch_tip == upstream_id { + return Ok(RebaseResult { + status: rebase_result::Status::RebaseResultStatusAlreadyUpToDate as i32, + head: None, + conflicts: vec![], + }); + } + + let result = duct::cmd( + "git", + [ + "--git-dir", + self.bare_dir.to_string_lossy().as_ref(), + "rev-list", + "--reverse", + &format!("{}..{}", upstream_id, branch_tip), + ], + ) + .stdout_capture() + .stderr_capture() + .unchecked() + .run()?; + if !result.status.success() { + return Err(GitError::CommandFailed { + status_code: result.status.code(), + stderr: String::from_utf8_lossy(&result.stderr).into_owned(), + }); + } + + let commits: Vec = String::from_utf8_lossy(&result.stdout) + .lines() + .map(str::trim) + .filter(|l| !l.is_empty()) + .map(String::from) + .collect(); + + if commits.is_empty() { + return Ok(RebaseResult { + status: rebase_result::Status::RebaseResultStatusAlreadyUpToDate as i32, + head: None, + conflicts: vec![], + }); + } + + let mut current_tip = upstream_id.clone(); + for commit_hex in &commits { + current_tip = + self.rebase_one_commit(commit_hex, ¤t_tip, request.committer.as_ref())?; + } + + self.update_branch_ref(&branch, ¤t_tip, Some(&branch_tip), false)?; + + Ok(RebaseResult { + status: rebase_result::Status::RebaseResultStatusRebased as i32, + head: Some(self.get_commit(crate::pb::GetCommitRequest { + repository: request.repository, + revision: Some(crate::pb::ObjectSelector { + selector: Some(crate::pb::object_selector::Selector::Revision( + crate::pb::ObjectName { + revision: current_tip, + }, + )), + }), + include_stats: false, + include_raw: false, + })?), + conflicts: vec![], + }) + } + + fn rebase_one_commit( + &self, + commit_hex: &str, + new_parent: &str, + committer: Option<&crate::pb::Signature>, + ) -> GitResult { + let repo = self.gix_repo()?; + let id = repo.rev_parse_single(commit_hex)?; + let obj = id + .object()? + .try_into_commit() + .map_err(|e| GitError::Gix(e.to_string()))?; + let message = obj.message_raw()?.to_string(); + let author = obj.author().ok(); + + let bare = self.bare_dir.to_string_lossy().into_owned(); + let tmp_index = tempfile::Builder::new() + .prefix("gitks-rebase-") + .tempfile_in(&self.bare_dir)?; + let idx_path = tmp_index.path().to_string_lossy().into_owned(); + + let read_tree = duct::cmd("git", ["--git-dir", bare.as_str(), "read-tree", new_parent]) + .env("GIT_INDEX_FILE", &idx_path) + .stdout_capture() + .stderr_capture() + .unchecked() + .run()?; + command_ok(read_tree)?; + + let diff = duct::cmd( + "git", + [ + "--git-dir", + bare.as_str(), + "format-patch", + "--stdout", + "--full-index", + "--binary", + "-1", + commit_hex, + ], + ) + .env("GIT_INDEX_FILE", &idx_path) + .stdout_capture() + .stderr_capture() + .unchecked() + .run()?; + let patch_data = command_ok(diff)?; + + let apply = duct::cmd( + "git", + [ + "--git-dir", + bare.as_str(), + "apply", + "--cached", + "--allow-empty", + "-", + ], + ) + .env("GIT_INDEX_FILE", &idx_path) + .stdin_bytes(patch_data.as_bytes()) + .stdout_capture() + .stderr_capture() + .unchecked() + .run()?; + if !apply.status.success() { + return Err(GitError::Internal(format!( + "rebase apply failed for {}: {}", + commit_hex, + String::from_utf8_lossy(&apply.stderr) + ))); + } + + let write_tree = duct::cmd("git", ["--git-dir", bare.as_str(), "write-tree"]) + .env("GIT_INDEX_FILE", &idx_path) + .stdout_capture() + .stderr_capture() + .unchecked() + .run()?; + let tree_id = command_ok(write_tree)?.trim().to_string(); + + let parents = vec![new_parent.to_string()]; + self.commit_tree( + &tree_id, + &parents, + &message, + author + .as_ref() + .map(|a| crate::commit::get_commit::gix_sig_to_pb(a)) + .as_ref(), + committer, + ) + } +} diff --git a/merge/resolve_merge_conflicts.rs b/merge/resolve_merge_conflicts.rs new file mode 100644 index 0000000..0996066 --- /dev/null +++ b/merge/resolve_merge_conflicts.rs @@ -0,0 +1,128 @@ +use crate::bare::GitBare; +use crate::commit::create_commit::command_ok; +use crate::error::{GitError, GitResult}; +use crate::pb::{MergeResult, ResolveMergeConflictsRequest, merge_result}; + +impl GitBare { + pub fn resolve_merge_conflicts( + &self, + request: ResolveMergeConflictsRequest, + ) -> GitResult { + let target_branch = request.target_branch.clone(); + let source_revision = match request.source.and_then(|s| s.selector) { + Some(crate::pb::object_selector::Selector::Oid(oid)) => oid.hex, + Some(crate::pb::object_selector::Selector::Revision(name)) => name.revision, + None => return Err(GitError::InvalidArgument("source is required".into())), + }; + + let repo = self.gix_repo()?; + let branch_ref = format!("refs/heads/{}", target_branch); + let target_id = repo + .find_reference(branch_ref.as_str()) + .ok() + .and_then(|mut r| r.peel_to_id().ok()) + .map(|id| id.to_string()) + .ok_or_else(|| GitError::RefNotFound(target_branch.clone()))?; + + let source_id = repo.rev_parse_single(source_revision.as_str())?.to_string(); + + let bare = self.bare_dir.to_string_lossy().into_owned(); + let tmp_index = tempfile::Builder::new() + .prefix("gitks-resolve-") + .tempfile_in(&self.bare_dir)?; + let idx_path = tmp_index.path().to_string_lossy().into_owned(); + + let read_tree = duct::cmd( + "git", + ["--git-dir", bare.as_str(), "read-tree", target_id.as_str()], + ) + .env("GIT_INDEX_FILE", &idx_path) + .stdout_capture() + .stderr_capture() + .unchecked() + .run()?; + command_ok(read_tree)?; + + for resolution in &request.resolutions { + let hash = duct::cmd( + "git", + ["--git-dir", bare.as_str(), "hash-object", "-w", "--stdin"], + ) + .stdin_bytes(resolution.content.clone()) + .stdout_capture() + .stderr_capture() + .unchecked() + .run()?; + let blob = command_ok(hash)?.trim().to_string(); + + let update = duct::cmd( + "git", + [ + "--git-dir", + bare.as_str(), + "update-index", + "--add", + "--cacheinfo", + "100644", + &blob, + &resolution.path, + ], + ) + .env("GIT_INDEX_FILE", &idx_path) + .env("GIT_WORK_TREE", bare.as_str()) + .stdout_capture() + .stderr_capture() + .unchecked() + .run()?; + command_ok(update)?; + } + + let write_tree = duct::cmd("git", ["--git-dir", bare.as_str(), "write-tree"]) + .env("GIT_INDEX_FILE", &idx_path) + .stdout_capture() + .stderr_capture() + .unchecked() + .run()?; + let tree_id = command_ok(write_tree)?.trim().to_string(); + + let message = if !request.message.is_empty() { + request.message.clone() + } else { + format!( + "Merge '{}' into {} (resolved conflicts)", + source_revision, target_branch + ) + }; + + let parents = vec![target_id.clone(), source_id.clone()]; + let commit_id = self.commit_tree( + &tree_id, + &parents, + &message, + request.committer.as_ref(), + request.committer.as_ref(), + )?; + + self.update_branch_ref(&target_branch, &commit_id, Some(&target_id), false)?; + + Ok(MergeResult { + status: merge_result::Status::MergeResultStatusMerged as i32, + commit: Some(self.get_commit(crate::pb::GetCommitRequest { + repository: request.repository, + revision: Some(crate::pb::ObjectSelector { + selector: Some(crate::pb::object_selector::Selector::Revision( + crate::pb::ObjectName { + revision: commit_id, + }, + )), + }), + include_stats: false, + include_raw: false, + })?), + merge_base: None, + conflicts: vec![], + stats: None, + message, + }) + } +} diff --git a/oid.rs b/oid.rs new file mode 100644 index 0000000..fb11e0e --- /dev/null +++ b/oid.rs @@ -0,0 +1,53 @@ +use serde::{Deserialize, Serialize}; + +/// The null OID representing "no object" in git protocol. +pub const ZERO_OID: &str = "0000000000000000000000000000000000000000"; + +#[derive(Deserialize, Serialize, Clone, Debug, PartialEq, Eq)] +pub struct ObjectId(pub String); + +impl ObjectId { + pub fn new(hex: impl AsRef) -> Self { + Self(hex.as_ref().to_lowercase()) + } + pub fn as_str(&self) -> &str { + &self.0 + } +} +impl std::fmt::Display for ObjectId { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.0) + } +} + +impl AsRef for ObjectId { + fn as_ref(&self) -> &str { + &self.0 + } +} + +pub fn hex_to_bytes(hex: &str) -> Result, crate::error::GitError> { + let hex = hex.trim(); + if !hex.len().is_multiple_of(2) { + return Err(crate::error::GitError::InvalidOid( + "hex oid has odd length".into(), + )); + } + + (0..hex.len()) + .step_by(2) + .map(|idx| { + u8::from_str_radix(&hex[idx..idx + 2], 16) + .map_err(|e| crate::error::GitError::InvalidOid(e.to_string())) + }) + .collect() +} + +impl TryFrom<&ObjectId> for gix::hash::ObjectId { + type Error = crate::error::GitError; + + fn try_from(id: &ObjectId) -> Result { + gix::hash::ObjectId::from_hex(id.as_str().as_bytes()) + .map_err(|e| crate::error::GitError::InvalidOid(format!("invalid hex oid: {e}"))) + } +} diff --git a/pack/advertise_refs.rs b/pack/advertise_refs.rs new file mode 100644 index 0000000..98c23d6 --- /dev/null +++ b/pack/advertise_refs.rs @@ -0,0 +1,57 @@ +use crate::bare::GitBare; +use crate::error::GitResult; +use crate::pb::{AdvertiseRefsRequest, AdvertiseRefsResponse, ReferenceAdvertisement}; + +impl GitBare { + pub fn advertise_refs( + &self, + _request: AdvertiseRefsRequest, + ) -> GitResult { + let repo = self.gix_repo()?; + let mut references = Vec::new(); + for r in repo.references()?.all()? { + let mut r = match r { + Ok(r) => r, + Err(_) => continue, + }; + let name = r.name().to_string(); + let target_oid = r.peel_to_id().ok().map(|id| self.oid_to_pb(id.to_string())); + let is_symbolic = r.target().try_id().is_none(); + let symbolic_target = if is_symbolic { + match r.target() { + gix::refs::TargetRef::Symbolic(name) => name.to_string(), + _ => String::new(), + } + } else { + String::new() + }; + // Peel past tags to get the commit OID if this is a tag ref + let peeled_oid = if name.starts_with("refs/tags/") { + r.peel_to_id().ok().map(|id| self.oid_to_pb(id.to_string())) + } else { + None + }; + references.push(ReferenceAdvertisement { + name, + target_oid, + peeled_oid, + symbolic: is_symbolic, + symbolic_target, + }); + } + // Sort by name for deterministic output + references.sort_by(|a, b| a.name.cmp(&b.name)); + Ok(AdvertiseRefsResponse { + references, + capabilities: vec![ + "report-status".into(), + "delete-refs".into(), + "side-band-64k".into(), + "ofs-delta".into(), + "multi_ack_detailed".into(), + "multi_ack".into(), + "symref=HEAD".into(), + ], + }) + } +} diff --git a/pack/fsck.rs b/pack/fsck.rs new file mode 100644 index 0000000..ed2305a --- /dev/null +++ b/pack/fsck.rs @@ -0,0 +1,45 @@ +use crate::bare::GitBare; +use crate::error::GitResult; +use crate::pb::{FsckRequest, FsckResponse}; + +impl GitBare { + pub fn fsck(&self, request: FsckRequest) -> GitResult { + let mut args = vec![ + "--git-dir".to_string(), + self.bare_dir.to_string_lossy().into_owned(), + "fsck".to_string(), + ]; + if request.strict { + args.push("--strict".into()); + } + if request.connectivity_only { + args.push("--connectivity-only".into()); + } + let result = duct::cmd("git", &args) + .stdout_capture() + .stderr_capture() + .unchecked() + .run()?; + let stdout = String::from_utf8_lossy(&result.stdout); + let stderr = String::from_utf8_lossy(&result.stderr); + let ok = result.status.success(); + let mut errors = Vec::new(); + let mut warnings = Vec::new(); + for line in stdout.lines().chain(stderr.lines()) { + if line.contains("error:") || line.contains("fatal:") { + errors.push( + line.trim_start_matches("error: ") + .trim_start_matches("fatal: ") + .to_string(), + ); + } else if line.contains("warning:") { + warnings.push(line.trim_start_matches("warning: ").to_string()); + } + } + Ok(FsckResponse { + ok, + errors, + warnings, + }) + } +} diff --git a/pack/index_pack.rs b/pack/index_pack.rs new file mode 100644 index 0000000..f7a9c50 --- /dev/null +++ b/pack/index_pack.rs @@ -0,0 +1,138 @@ +use std::io::Write; + +use crate::bare::GitBare; +use crate::error::{GitError, GitResult}; +use crate::pb::{IndexPackRequest, IndexPackResponse}; + +impl GitBare { + /// Index a pack file from streamed input. + /// + /// Client-streaming → unary response. + /// Collects all input chunks into a single pack, then runs `git index-pack`. + pub fn index_pack(&self, inputs: Vec) -> GitResult { + // Reassemble all chunks into a single pack data buffer + let mut pack_data = Vec::new(); + let mut strict = false; + let mut keep = false; + + for input in &inputs { + pack_data.extend_from_slice(&input.data); + if input.strict { + strict = true; + } + if input.keep { + keep = true; + } + } + + if pack_data.is_empty() { + return Err(GitError::InvalidArgument("empty pack data".into())); + } + + let pack_dir = self.bare_dir.join("objects").join("pack"); + std::fs::create_dir_all(&pack_dir).map_err(GitError::Io)?; + + // Write pack data to a unique temp file in the pack directory. + let mut tmp_file = tempfile::Builder::new() + .prefix("tmp_index_pack_") + .tempfile_in(&pack_dir) + .map_err(GitError::Io)?; + tmp_file.write_all(&pack_data).map_err(GitError::Io)?; + let tmp_path = tmp_file.path().to_path_buf(); + + let mut args = vec![ + "--git-dir".to_string(), + self.bare_dir.to_string_lossy().into_owned(), + "index-pack".to_string(), + ]; + if strict { + args.push("--strict".into()); + } + if keep { + args.push("--keep".into()); + } + args.push(tmp_path.to_string_lossy().into_owned()); + + let result = duct::cmd("git", &args) + .stdout_capture() + .stderr_capture() + .unchecked() + .run()?; + + drop(tmp_file); + + if !result.status.success() { + return Err(GitError::CommandFailed { + status_code: result.status.code(), + stderr: String::from_utf8_lossy(&result.stderr).into_owned(), + }); + } + + // Parse the output to extract the pack hash + let output = String::from_utf8_lossy(&result.stdout); + let stderr = String::from_utf8_lossy(&result.stderr); + let all_output = format!("{output}\n{stderr}"); + + // git index-pack outputs the .idx and .pack filenames + // e.g. "... pack-.pack ... pack-.idx" + let pack_hash = all_output + .lines() + .filter_map(|line| { + // Look for the hash after "pack-" and before ".idx" or ".pack" + let trimmed = line.trim(); + if let Some(idx) = trimmed.find("pack-") { + let rest = &trimmed[idx + 5..]; + if let Some(end) = rest.find('.') { + let hex = &rest[..end]; + if hex.len() == 40 || hex.len() == 64 { + return Some(hex.to_string()); + } + } + } + None + }) + .next(); + + // Try to get object count from .idx if it exists + let mut object_count = 0u64; + if let Some(ref hash) = pack_hash { + let idx_path = pack_dir.join(format!("pack-{}.idx", hash)); + if idx_path.exists() { + let verify = duct::cmd( + "git", + [ + "--git-dir", + self.bare_dir.to_string_lossy().as_ref(), + "verify-pack", + "-v", + idx_path.to_string_lossy().as_ref(), + ], + ) + .stdout_capture() + .stderr_capture() + .unchecked() + .run(); + if let Ok(v) = verify { + let out = String::from_utf8_lossy(&v.stdout); + object_count = out + .lines() + .filter(|l| { + let parts: Vec<&str> = l.split_whitespace().collect(); + parts.len() >= 3 + && parts + .first() + .map(|s| s.len() == 40 || s.len() == 64) + .unwrap_or(false) + }) + .count() as u64; + } + } + } + + Ok(IndexPackResponse { + pack_hash: pack_hash.map(|h| self.oid_to_pb(h)), + object_count, + stderr: stderr.into_owned(), + }) + } +} diff --git a/pack/list_packfiles.rs b/pack/list_packfiles.rs new file mode 100644 index 0000000..cf20476 --- /dev/null +++ b/pack/list_packfiles.rs @@ -0,0 +1,92 @@ +use crate::bare::GitBare; +use crate::error::GitError; +use crate::paginate; +use crate::pb::{ListPackfilesRequest, ListPackfilesResponse, PackfileInfo}; + +impl GitBare { + pub fn list_packfiles( + &self, + request: ListPackfilesRequest, + ) -> crate::error::GitResult { + let pack_dir = self.bare_dir.join("objects").join("pack"); + let mut packfiles = Vec::new(); + + if pack_dir.exists() { + for entry in std::fs::read_dir(&pack_dir).map_err(GitError::Io)? { + let entry = entry.map_err(GitError::Io)?; + let name = entry.file_name().to_string_lossy().into_owned(); + if !name.ends_with(".pack") { + continue; + } + let metadata = entry.metadata().map_err(GitError::Io)?; + let base_name = name.trim_end_matches(".pack"); + let idx_name = format!("{base_name}.idx"); + let bmp_name = format!("{base_name}.bitmap"); + let rev_name = format!("{base_name}.rev"); + let keep_name = format!("{base_name}.keep"); + + let pack_hash = base_name + .strip_prefix("pack-") + .filter(|hex| !hex.is_empty()) + .map(|hex| self.oid_to_pb(hex)); + + // Count objects + let mut object_count = 0u64; + if let Some(hash_str) = base_name.strip_prefix("pack-") { + let idx_path = pack_dir.join(format!("pack-{hash_str}.idx")); + if idx_path.exists() { + let verify = duct::cmd( + "git", + [ + "--git-dir", + self.bare_dir.to_string_lossy().as_ref(), + "verify-pack", + "-v", + idx_path.to_string_lossy().as_ref(), + ], + ) + .stdout_capture() + .stderr_capture() + .unchecked() + .run(); + if let Ok(v) = verify { + let out = String::from_utf8_lossy(&v.stdout); + object_count = out + .lines() + .filter(|l| { + let parts: Vec<&str> = l.split_whitespace().collect(); + parts.len() >= 3 + && parts + .first() + .map(|s| s.len() == 40 || s.len() == 64) + .unwrap_or(false) + }) + .count() as u64; + } + } + } + + packfiles.push(PackfileInfo { + name, + pack_hash, + size_bytes: metadata.len(), + index_size_bytes: pack_dir + .join(&idx_name) + .metadata() + .map(|m| m.len()) + .unwrap_or(0), + object_count, + has_bitmap: pack_dir.join(&bmp_name).exists(), + has_rev_index: pack_dir.join(&rev_name).exists(), + kept: pack_dir.join(&keep_name).exists(), + }); + } + } + packfiles.sort_by(|a, b| a.name.cmp(&b.name)); + let (packfiles, page_info) = paginate::paginate(&packfiles, request.pagination.as_ref()); + Ok(ListPackfilesResponse { + packfiles, + page_info: Some(page_info), + }) + } +} diff --git a/pack/mod.rs b/pack/mod.rs new file mode 100644 index 0000000..53188e0 --- /dev/null +++ b/pack/mod.rs @@ -0,0 +1,7 @@ +pub mod advertise_refs; +pub mod fsck; +pub mod index_pack; +pub mod list_packfiles; +pub mod pack_objects; +pub mod receive_pack; +pub mod upload_pack; diff --git a/pack/pack_objects.rs b/pack/pack_objects.rs new file mode 100644 index 0000000..68a4d66 --- /dev/null +++ b/pack/pack_objects.rs @@ -0,0 +1,160 @@ +use std::process::Stdio; + +use tokio::io::{AsyncReadExt, AsyncWriteExt}; +use tokio::process::Command; +use tokio_stream::wrappers::ReceiverStream; + +use crate::bare::GitBare; +use crate::pb::PackfileChunk; + +impl GitBare { + /// Pack objects using git-pack-objects --stdout. + /// + /// Unary request → server-streaming response. + /// The returned stream yields `PackfileChunk` chunks as pack data is produced. + pub async fn pack_objects( + &self, + request: crate::pb::PackObjectsRequest, + ) -> Result>, tonic::Status> { + let bare_dir = self.bare_dir.clone(); + let bare_dir_str = bare_dir.to_string_lossy().into_owned(); + + let (tx, rx) = tokio::sync::mpsc::channel(8); + + tokio::spawn(async move { + let opts = request.options.as_ref(); + let has_wants = opts.is_some_and(|o| !o.wants.is_empty()); + + let mut args = vec![ + "--git-dir".to_string(), + bare_dir_str, + "pack-objects".to_string(), + "--stdout".to_string(), + ]; + + // --all is mutually exclusive with explicit revision selection. + if !has_wants { + args.push("--all".into()); + } else { + args.push("--revs".into()); + } + + if opts.is_some_and(|o| o.thin_pack) { + args.push("--thin".into()); + } + if opts.is_some_and(|o| !o.use_bitmaps) { + args.push("--no-use-bitmaps".into()); + } + if opts.is_some_and(|o| o.delta_base_offset) { + args.push("--delta-base-offset".into()); + } + let stdin_data = generate_pack_input(&request); + + let mut child = match Command::new("git") + .args(&args) + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + { + Ok(c) => c, + Err(e) => { + let _ = tx + .send(Err(tonic::Status::internal(format!( + "failed to spawn git pack-objects: {e}" + )))) + .await; + return; + } + }; + + let mut stdin = child.stdin.take(); + let mut stdout = child.stdout.take(); + let mut stderr = child.stderr.take(); + + let stdin_task = async move { + if let Some(mut stdin) = stdin.take() { + let _ = stdin.write_all(&stdin_data).await; + } + }; + + let stdout_task = { + let tx = tx.clone(); + async move { + if let Some(mut stdout) = stdout.take() { + let mut buf = vec![0u8; 65536]; + loop { + match stdout.read(&mut buf).await { + Ok(0) => break, + Ok(n) => { + if tx + .send(Ok(PackfileChunk { + data: buf[..n].to_vec(), + })) + .await + .is_err() + { + break; + } + } + Err(e) => { + let _ = tx + .send(Err(tonic::Status::internal(format!( + "read error: {e}" + )))) + .await; + break; + } + } + } + } + } + }; + + let stderr_task = { + let tx = tx.clone(); + async move { + if let Some(mut stderr) = stderr.take() { + let mut s = String::new(); + if stderr.read_to_string(&mut s).await.is_ok() && !s.is_empty() { + let _ = tx.send(Err(tonic::Status::internal(s))).await; + } + } + } + }; + + tokio::join!(stdin_task, stdout_task, stderr_task); + + match child.wait().await { + Ok(status) if !status.success() => { + let _ = tx + .send(Err(tonic::Status::internal( + "git pack-objects exited with error", + ))) + .await; + } + Err(e) => { + let _ = tx + .send(Err(tonic::Status::internal(format!("wait error: {e}")))) + .await; + } + _ => {} + } + }); + + Ok(ReceiverStream::new(rx)) + } +} + +fn generate_pack_input(req: &crate::pb::PackObjectsRequest) -> Vec { + let mut input = String::new(); + if let Some(opts) = req.options.as_ref() { + for want in &opts.wants { + input.push_str(&format!("{}\n", want.hex)); + } + for have in &opts.haves { + input.push_str(&format!("^{}\n", have.hex)); + } + } + input.into_bytes() +} diff --git a/pack/receive_pack.rs b/pack/receive_pack.rs new file mode 100644 index 0000000..d4f2090 --- /dev/null +++ b/pack/receive_pack.rs @@ -0,0 +1,142 @@ +use std::process::Stdio; + +use tokio::io::{AsyncReadExt, AsyncWriteExt}; +use tokio::process::Command; +use tokio_stream::StreamExt; +use tokio_stream::wrappers::ReceiverStream; + +use crate::bare::GitBare; +use crate::pb::ReceivePackResponse; + +impl GitBare { + /// Receive pack data using git-receive-pack with true concurrent streaming. + /// + /// Client-streaming input → server-streaming output. + /// Stdin packets are forwarded to the child process as they arrive from the client, + /// while stdout is concurrently read and streamed back via a channel. + pub async fn receive_pack( + &self, + input: impl tokio_stream::Stream> + + Send + + 'static, + ) -> Result>, tonic::Status> { + let bare_dir = self.bare_dir.to_string_lossy().into_owned(); + + let (tx, rx) = tokio::sync::mpsc::channel(16); + + let stream = Box::pin(input); + tokio::spawn(async move { + let stream = stream; + let mut child = match Command::new("git") + .arg("--git-dir") + .arg(&bare_dir) + .arg("receive-pack") + .arg(&bare_dir) + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + { + Ok(c) => c, + Err(e) => { + let _ = tx + .send(Err(tonic::Status::internal(format!( + "failed to spawn git receive-pack: {e}" + )))) + .await; + return; + } + }; + + let mut stdin = child.stdin.take(); + let mut stdout = child.stdout.take(); + let mut stderr = child.stderr.take(); + + let stdin_task = { + let mut stream = stream; + async move { + if let Some(mut stdin) = stdin.take() { + while let Some(result) = stream.next().await { + match result { + Ok(req) => { + if stdin.write_all(&req.packet).await.is_err() { + break; + } + if req.done { + break; + } + } + Err(_) => break, + } + } + drop(stdin); + } + } + }; + + let stdout_task = { + let tx = tx.clone(); + async move { + if let Some(mut stdout) = stdout.take() { + let mut buf = vec![0u8; 65536]; + loop { + match stdout.read(&mut buf).await { + Ok(0) => break, + Ok(n) => { + if tx + .send(Ok(ReceivePackResponse { + packet: buf[..n].to_vec(), + stderr: String::new(), + })) + .await + .is_err() + { + break; + } + } + Err(_) => break, + } + } + } + } + }; + + let stderr_task = { + let tx = tx.clone(); + async move { + if let Some(mut stderr) = stderr.take() { + let mut s = String::new(); + if stderr.read_to_string(&mut s).await.is_ok() && !s.is_empty() { + let _ = tx + .send(Ok(ReceivePackResponse { + packet: Vec::new(), + stderr: s, + })) + .await; + } + } + } + }; + + tokio::join!(stdin_task, stdout_task, stderr_task); + + match child.wait().await { + Ok(status) if !status.success() => { + let _ = tx + .send(Err(tonic::Status::internal( + "git receive-pack exited with error", + ))) + .await; + } + Err(e) => { + let _ = tx + .send(Err(tonic::Status::internal(format!("wait error: {e}")))) + .await; + } + _ => {} + } + }); + + Ok(ReceiverStream::new(rx)) + } +} diff --git a/pack/upload_pack.rs b/pack/upload_pack.rs new file mode 100644 index 0000000..0cde407 --- /dev/null +++ b/pack/upload_pack.rs @@ -0,0 +1,147 @@ +use std::process::Stdio; + +use tokio::io::{AsyncReadExt, AsyncWriteExt}; +use tokio::process::Command; +use tokio_stream::StreamExt; +use tokio_stream::wrappers::ReceiverStream; + +use crate::bare::GitBare; +use crate::pb::UploadPackResponse; + +impl GitBare { + /// Upload pack data using git-upload-pack with true concurrent streaming. + /// + /// Client-streaming input → server-streaming output. + /// Stdin packets are forwarded to the child process as they arrive from the client, + /// while stdout is concurrently read and streamed back via a channel. + pub async fn upload_pack( + &self, + input: impl tokio_stream::Stream> + + Send + + 'static, + ) -> Result>, tonic::Status> { + let bare_dir = self.bare_dir.to_string_lossy().into_owned(); + + let (tx, rx) = tokio::sync::mpsc::channel(16); + + // Move input into the spawned task to make it 'static + let stream = Box::pin(input); + tokio::spawn(async move { + let stream = stream; + let mut child = match Command::new("git") + .arg("--git-dir") + .arg(&bare_dir) + .arg("upload-pack") + .arg(&bare_dir) + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + { + Ok(c) => c, + Err(e) => { + let _ = tx + .send(Err(tonic::Status::internal(format!( + "failed to spawn git upload-pack: {e}" + )))) + .await; + return; + } + }; + + let mut stdin = child.stdin.take(); + let mut stdout = child.stdout.take(); + let mut stderr = child.stderr.take(); + + // Concurrent: write stdin packets, read stdout chunks, read stderr + let stdin_task = { + let mut stream = stream; + async move { + if let Some(mut stdin) = stdin.take() { + while let Some(result) = stream.next().await { + match result { + Ok(req) => { + if stdin.write_all(&req.packet).await.is_err() { + break; + } + if req.done { + break; + } + } + Err(_) => break, + } + } + // Close stdin to signal end-of-input + drop(stdin); + } + } + }; + + let stdout_task = { + let tx = tx.clone(); + async move { + if let Some(mut stdout) = stdout.take() { + let mut buf = vec![0u8; 65536]; + loop { + match stdout.read(&mut buf).await { + Ok(0) => break, + Ok(n) => { + if tx + .send(Ok(UploadPackResponse { + packet: buf[..n].to_vec(), + stderr: String::new(), + })) + .await + .is_err() + { + break; + } + } + Err(_) => break, + } + } + } + } + }; + + let stderr_task = { + let tx = tx.clone(); + async move { + if let Some(mut stderr) = stderr.take() { + let mut s = String::new(); + if stderr.read_to_string(&mut s).await.is_ok() && !s.is_empty() { + let _ = tx + .send(Ok(UploadPackResponse { + packet: Vec::new(), + stderr: s, + })) + .await; + } + } + } + }; + + // Run all three concurrently + tokio::join!(stdin_task, stdout_task, stderr_task); + + // Wait for child exit + match child.wait().await { + Ok(status) if !status.success() => { + let _ = tx + .send(Err(tonic::Status::internal( + "git upload-pack exited with error", + ))) + .await; + } + Err(e) => { + let _ = tx + .send(Err(tonic::Status::internal(format!("wait error: {e}")))) + .await; + } + _ => {} + } + }); + + Ok(ReceiverStream::new(rx)) + } +} diff --git a/paginate.rs b/paginate.rs new file mode 100644 index 0000000..e1bf07e --- /dev/null +++ b/paginate.rs @@ -0,0 +1,46 @@ +use crate::pb::{PageInfo, Pagination}; + +/// Simple offset-based pagination over an in-memory slice. +/// The `page_token` is a decimal string encoding the start offset. +pub fn paginate(items: &[T], pagination: Option<&Pagination>) -> (Vec, PageInfo) { + let page_size = pagination + .map(|p| p.page_size as usize) + .unwrap_or(items.len().max(1)) + .max(1); + + let start_offset = pagination + .and_then(|p| { + if p.page_token.is_empty() { + None + } else { + p.page_token.parse::().ok() + } + }) + .unwrap_or(0) + .min(items.len()); + + let end = std::cmp::min(start_offset + page_size, items.len()); + let has_next = end < items.len(); + let next_page_token = if has_next { + end.to_string() + } else { + String::new() + }; + + ( + items[start_offset..end].to_vec(), + PageInfo { + next_page_token, + has_next_page: has_next, + total_count: items.len() as u64, + }, + ) +} + +/// Apply sort direction. Ascending = no-op (preserves order). +/// Descending reverses the slice. +pub fn apply_sort(items: &mut [T], sort_direction: i32) { + if sort_direction == crate::pb::SortDirection::Desc as i32 { + items.reverse(); + } +} diff --git a/pb/gitks.rs b/pb/gitks.rs new file mode 100644 index 0000000..1a2d667 --- /dev/null +++ b/pb/gitks.rs @@ -0,0 +1,8923 @@ +// This file is @generated by prost-build. +/// Canonical object id. `value` preserves the original binary representation used +/// by the existing API; `hex` is the normalized lowercase hex form for clients. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct Oid { + #[prost(bytes = "vec", tag = "1")] + pub value: ::prost::alloc::vec::Vec, + #[prost(string, tag = "2")] + pub hex: ::prost::alloc::string::String, + #[prost(enumeration = "ObjectFormat", tag = "3")] + pub format: i32, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ObjectName { + /// Revision expression, refname, oid hex, or pseudo-ref such as HEAD. + #[prost(string, tag = "1")] + pub revision: ::prost::alloc::string::String, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ObjectSelector { + #[prost(oneof = "object_selector::Selector", tags = "1, 2")] + pub selector: ::core::option::Option, +} +/// Nested message and enum types in `ObjectSelector`. +pub mod object_selector { + #[derive(Clone, PartialEq, ::prost::Oneof)] + pub enum Selector { + #[prost(message, tag = "1")] + Oid(super::Oid), + #[prost(message, tag = "2")] + Revision(super::ObjectName), + } +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ObjectIdentity { + #[prost(message, optional, tag = "1")] + pub oid: ::core::option::Option, + #[prost(enumeration = "ObjectType", tag = "2")] + pub r#type: i32, + #[prost(int64, tag = "3")] + pub size: i64, + #[prost(string, tag = "4")] + pub abbreviated_oid: ::prost::alloc::string::String, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct Pagination { + #[prost(uint32, tag = "1")] + pub page_size: u32, + #[prost(string, tag = "2")] + pub page_token: ::prost::alloc::string::String, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct PageInfo { + #[prost(string, tag = "1")] + pub next_page_token: ::prost::alloc::string::String, + #[prost(bool, tag = "2")] + pub has_next_page: bool, + #[prost(uint64, tag = "3")] + pub total_count: u64, +} +/// Git object hash algorithm. GitHub and Gitaly both need to support SHA-1 today +/// and SHA-256 repositories as they become more common. +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] +#[repr(i32)] +pub enum ObjectFormat { + Unspecified = 0, + Sha1 = 1, + Sha256 = 2, +} +impl ObjectFormat { + /// String value of the enum field names used in the ProtoBuf definition. + /// + /// The values are not transformed in any way and thus are considered stable + /// (if the ProtoBuf definition does not change) and safe for programmatic use. + pub fn as_str_name(&self) -> &'static str { + match self { + Self::Unspecified => "OBJECT_FORMAT_UNSPECIFIED", + Self::Sha1 => "OBJECT_FORMAT_SHA1", + Self::Sha256 => "OBJECT_FORMAT_SHA256", + } + } + /// Creates an enum from field names used in the ProtoBuf definition. + pub fn from_str_name(value: &str) -> ::core::option::Option { + match value { + "OBJECT_FORMAT_UNSPECIFIED" => Some(Self::Unspecified), + "OBJECT_FORMAT_SHA1" => Some(Self::Sha1), + "OBJECT_FORMAT_SHA256" => Some(Self::Sha256), + _ => None, + } + } +} +/// Git object kind. +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] +#[repr(i32)] +pub enum ObjectType { + Unspecified = 0, + Commit = 1, + Tree = 2, + Blob = 3, + Tag = 4, +} +impl ObjectType { + /// String value of the enum field names used in the ProtoBuf definition. + /// + /// The values are not transformed in any way and thus are considered stable + /// (if the ProtoBuf definition does not change) and safe for programmatic use. + pub fn as_str_name(&self) -> &'static str { + match self { + Self::Unspecified => "OBJECT_TYPE_UNSPECIFIED", + Self::Commit => "OBJECT_TYPE_COMMIT", + Self::Tree => "OBJECT_TYPE_TREE", + Self::Blob => "OBJECT_TYPE_BLOB", + Self::Tag => "OBJECT_TYPE_TAG", + } + } + /// Creates an enum from field names used in the ProtoBuf definition. + pub fn from_str_name(value: &str) -> ::core::option::Option { + match value { + "OBJECT_TYPE_UNSPECIFIED" => Some(Self::Unspecified), + "OBJECT_TYPE_COMMIT" => Some(Self::Commit), + "OBJECT_TYPE_TREE" => Some(Self::Tree), + "OBJECT_TYPE_BLOB" => Some(Self::Blob), + "OBJECT_TYPE_TAG" => Some(Self::Tag), + _ => None, + } + } +} +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] +#[repr(i32)] +pub enum SortDirection { + Unspecified = 0, + Asc = 1, + Desc = 2, +} +impl SortDirection { + /// String value of the enum field names used in the ProtoBuf definition. + /// + /// The values are not transformed in any way and thus are considered stable + /// (if the ProtoBuf definition does not change) and safe for programmatic use. + pub fn as_str_name(&self) -> &'static str { + match self { + Self::Unspecified => "SORT_DIRECTION_UNSPECIFIED", + Self::Asc => "SORT_DIRECTION_ASC", + Self::Desc => "SORT_DIRECTION_DESC", + } + } + /// Creates an enum from field names used in the ProtoBuf definition. + pub fn from_str_name(value: &str) -> ::core::option::Option { + match value { + "SORT_DIRECTION_UNSPECIFIED" => Some(Self::Unspecified), + "SORT_DIRECTION_ASC" => Some(Self::Asc), + "SORT_DIRECTION_DESC" => Some(Self::Desc), + _ => None, + } + } +} +/// Repository identity used by storage-facing RPCs. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct RepositoryHeader { + /// Logical storage shard or disk name. + #[prost(string, tag = "1")] + pub storage_name: ::prost::alloc::string::String, + /// Path relative to the storage root, usually ending in `.git` for bare repos. + #[prost(string, tag = "2")] + pub relative_path: ::prost::alloc::string::String, + /// Optional absolute path for embedded/local deployments. + #[prost(string, tag = "3")] + pub storage_path: ::prost::alloc::string::String, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct Repository { + #[prost(message, optional, tag = "1")] + pub header: ::core::option::Option, + #[prost(bool, tag = "2")] + pub bare: bool, + #[prost(bool, tag = "3")] + pub empty: bool, + #[prost(enumeration = "ObjectFormat", tag = "4")] + pub object_format: i32, + #[prost(string, tag = "5")] + pub default_branch: ::prost::alloc::string::String, + #[prost(string, tag = "6")] + pub git_object_directory: ::prost::alloc::string::String, + #[prost(string, repeated, tag = "7")] + pub git_alternate_object_directories: ::prost::alloc::vec::Vec< + ::prost::alloc::string::String, + >, +} +#[derive(Clone, Copy, PartialEq, ::prost::Message)] +pub struct RepositoryStatistics { + #[prost(uint64, tag = "1")] + pub size_bytes: u64, + #[prost(uint64, tag = "2")] + pub loose_object_count: u64, + #[prost(uint64, tag = "3")] + pub packed_object_count: u64, + #[prost(uint64, tag = "4")] + pub packfile_count: u64, + #[prost(uint64, tag = "5")] + pub reference_count: u64, + #[prost(uint64, tag = "6")] + pub commit_graph_size_bytes: u64, + #[prost(uint64, tag = "7")] + pub multi_pack_index_size_bytes: u64, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct RepositoryConfigEntry { + #[prost(string, tag = "1")] + pub key: ::prost::alloc::string::String, + #[prost(string, repeated, tag = "2")] + pub values: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct RepositoryObjectFormatRequest { + #[prost(message, optional, tag = "1")] + pub repository: ::core::option::Option, +} +#[derive(Clone, Copy, PartialEq, ::prost::Message)] +pub struct RepositoryObjectFormatResponse { + #[prost(enumeration = "ObjectFormat", tag = "1")] + pub object_format: i32, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct GetRepositoryRequest { + #[prost(message, optional, tag = "1")] + pub repository: ::core::option::Option, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct InitRepositoryRequest { + #[prost(message, optional, tag = "1")] + pub repository: ::core::option::Option, + #[prost(bool, tag = "2")] + pub bare: bool, + #[prost(enumeration = "ObjectFormat", tag = "3")] + pub object_format: i32, + #[prost(string, tag = "4")] + pub initial_branch: ::prost::alloc::string::String, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct DeleteRepositoryRequest { + #[prost(message, optional, tag = "1")] + pub repository: ::core::option::Option, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct RepositoryExistsRequest { + #[prost(message, optional, tag = "1")] + pub repository: ::core::option::Option, +} +#[derive(Clone, Copy, PartialEq, ::prost::Message)] +pub struct RepositoryExistsResponse { + #[prost(bool, tag = "1")] + pub exists: bool, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct GetDefaultBranchRequest { + #[prost(message, optional, tag = "1")] + pub repository: ::core::option::Option, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct GetDefaultBranchResponse { + #[prost(string, tag = "1")] + pub name: ::prost::alloc::string::String, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct SetDefaultBranchRequest { + #[prost(message, optional, tag = "1")] + pub repository: ::core::option::Option, + #[prost(string, tag = "2")] + pub name: ::prost::alloc::string::String, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct GetRepositoryConfigRequest { + #[prost(message, optional, tag = "1")] + pub repository: ::core::option::Option, + #[prost(string, repeated, tag = "2")] + pub keys: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct GetRepositoryConfigResponse { + #[prost(message, repeated, tag = "1")] + pub entries: ::prost::alloc::vec::Vec, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct SetRepositoryConfigRequest { + #[prost(message, optional, tag = "1")] + pub repository: ::core::option::Option, + #[prost(message, repeated, tag = "2")] + pub entries: ::prost::alloc::vec::Vec, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct RepositoryStatisticsRequest { + #[prost(message, optional, tag = "1")] + pub repository: ::core::option::Option, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct RepositoryHealthRequest { + #[prost(message, optional, tag = "1")] + pub repository: ::core::option::Option, + #[prost(bool, tag = "2")] + pub connectivity_only: bool, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct RepositoryHealthResponse { + #[prost(bool, tag = "1")] + pub ok: bool, + #[prost(string, repeated, tag = "2")] + pub warnings: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, + #[prost(string, repeated, tag = "3")] + pub errors: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, + #[prost(message, optional, tag = "4")] + pub statistics: ::core::option::Option, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct GarbageCollectRequest { + #[prost(message, optional, tag = "1")] + pub repository: ::core::option::Option, + #[prost(bool, tag = "2")] + pub prune: bool, + #[prost(bool, tag = "3")] + pub aggressive: bool, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct RepackRequest { + #[prost(message, optional, tag = "1")] + pub repository: ::core::option::Option, + #[prost(bool, tag = "2")] + pub full: bool, + #[prost(bool, tag = "3")] + pub write_bitmaps: bool, + #[prost(bool, tag = "4")] + pub write_multi_pack_index: bool, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct WriteCommitGraphRequest { + #[prost(message, optional, tag = "1")] + pub repository: ::core::option::Option, + #[prost(bool, tag = "2")] + pub replace: bool, + #[prost(bool, tag = "3")] + pub split: bool, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct RepositoryMaintenanceResponse { + #[prost(bool, tag = "1")] + pub ok: bool, + #[prost(string, tag = "2")] + pub stdout: ::prost::alloc::string::String, + #[prost(string, tag = "3")] + pub stderr: ::prost::alloc::string::String, +} +/// Generated client implementations. +pub mod repository_service_client { + #![allow( + unused_variables, + dead_code, + missing_docs, + clippy::wildcard_imports, + clippy::let_unit_value, + )] + use tonic::codegen::*; + use tonic::codegen::http::Uri; + #[derive(Debug, Clone)] + pub struct RepositoryServiceClient { + inner: tonic::client::Grpc, + } + impl RepositoryServiceClient { + /// Attempt to create a new client by connecting to a given endpoint. + pub async fn connect(dst: D) -> Result + where + D: TryInto, + D::Error: Into, + { + let conn = tonic::transport::Endpoint::new(dst)?.connect().await?; + Ok(Self::new(conn)) + } + } + impl RepositoryServiceClient + where + T: tonic::client::GrpcService, + T::Error: Into, + T::ResponseBody: Body + std::marker::Send + 'static, + ::Error: Into + std::marker::Send, + { + pub fn new(inner: T) -> Self { + let inner = tonic::client::Grpc::new(inner); + Self { inner } + } + pub fn with_origin(inner: T, origin: Uri) -> Self { + let inner = tonic::client::Grpc::with_origin(inner, origin); + Self { inner } + } + pub fn with_interceptor( + inner: T, + interceptor: F, + ) -> RepositoryServiceClient> + where + F: tonic::service::Interceptor, + T::ResponseBody: Default, + T: tonic::codegen::Service< + http::Request, + Response = http::Response< + >::ResponseBody, + >, + >, + , + >>::Error: Into + std::marker::Send + std::marker::Sync, + { + RepositoryServiceClient::new(InterceptedService::new(inner, interceptor)) + } + /// Compress requests with the given encoding. + /// + /// This requires the server to support it otherwise it might respond with an + /// error. + #[must_use] + pub fn send_compressed(mut self, encoding: CompressionEncoding) -> Self { + self.inner = self.inner.send_compressed(encoding); + self + } + /// Enable decompressing responses. + #[must_use] + pub fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self { + self.inner = self.inner.accept_compressed(encoding); + self + } + /// Limits the maximum size of a decoded message. + /// + /// Default: `4MB` + #[must_use] + pub fn max_decoding_message_size(mut self, limit: usize) -> Self { + self.inner = self.inner.max_decoding_message_size(limit); + self + } + /// Limits the maximum size of an encoded message. + /// + /// Default: `usize::MAX` + #[must_use] + pub fn max_encoding_message_size(mut self, limit: usize) -> Self { + self.inner = self.inner.max_encoding_message_size(limit); + self + } + pub async fn get_repository( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result, tonic::Status> { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic::codec::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/gitks.RepositoryService/GetRepository", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert(GrpcMethod::new("gitks.RepositoryService", "GetRepository")); + self.inner.unary(req, path, codec).await + } + pub async fn init_repository( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result, tonic::Status> { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic::codec::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/gitks.RepositoryService/InitRepository", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert(GrpcMethod::new("gitks.RepositoryService", "InitRepository")); + self.inner.unary(req, path, codec).await + } + pub async fn delete_repository( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result, tonic::Status> { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic::codec::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/gitks.RepositoryService/DeleteRepository", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert(GrpcMethod::new("gitks.RepositoryService", "DeleteRepository")); + self.inner.unary(req, path, codec).await + } + pub async fn repository_exists( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + > { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic::codec::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/gitks.RepositoryService/RepositoryExists", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert(GrpcMethod::new("gitks.RepositoryService", "RepositoryExists")); + self.inner.unary(req, path, codec).await + } + pub async fn get_object_format( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + > { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic::codec::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/gitks.RepositoryService/GetObjectFormat", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert(GrpcMethod::new("gitks.RepositoryService", "GetObjectFormat")); + self.inner.unary(req, path, codec).await + } + pub async fn get_default_branch( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + > { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic::codec::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/gitks.RepositoryService/GetDefaultBranch", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert(GrpcMethod::new("gitks.RepositoryService", "GetDefaultBranch")); + self.inner.unary(req, path, codec).await + } + pub async fn set_default_branch( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result, tonic::Status> { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic::codec::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/gitks.RepositoryService/SetDefaultBranch", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert(GrpcMethod::new("gitks.RepositoryService", "SetDefaultBranch")); + self.inner.unary(req, path, codec).await + } + pub async fn get_repository_config( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + > { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic::codec::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/gitks.RepositoryService/GetRepositoryConfig", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert( + GrpcMethod::new("gitks.RepositoryService", "GetRepositoryConfig"), + ); + self.inner.unary(req, path, codec).await + } + pub async fn set_repository_config( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result, tonic::Status> { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic::codec::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/gitks.RepositoryService/SetRepositoryConfig", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert( + GrpcMethod::new("gitks.RepositoryService", "SetRepositoryConfig"), + ); + self.inner.unary(req, path, codec).await + } + pub async fn get_repository_statistics( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + > { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic::codec::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/gitks.RepositoryService/GetRepositoryStatistics", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert( + GrpcMethod::new("gitks.RepositoryService", "GetRepositoryStatistics"), + ); + self.inner.unary(req, path, codec).await + } + pub async fn check_repository_health( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + > { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic::codec::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/gitks.RepositoryService/CheckRepositoryHealth", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert( + GrpcMethod::new("gitks.RepositoryService", "CheckRepositoryHealth"), + ); + self.inner.unary(req, path, codec).await + } + pub async fn garbage_collect( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + > { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic::codec::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/gitks.RepositoryService/GarbageCollect", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert(GrpcMethod::new("gitks.RepositoryService", "GarbageCollect")); + self.inner.unary(req, path, codec).await + } + pub async fn repack( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + > { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic::codec::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/gitks.RepositoryService/Repack", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert(GrpcMethod::new("gitks.RepositoryService", "Repack")); + self.inner.unary(req, path, codec).await + } + pub async fn write_commit_graph( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + > { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic::codec::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/gitks.RepositoryService/WriteCommitGraph", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert(GrpcMethod::new("gitks.RepositoryService", "WriteCommitGraph")); + self.inner.unary(req, path, codec).await + } + } +} +/// Generated server implementations. +pub mod repository_service_server { + #![allow( + unused_variables, + dead_code, + missing_docs, + clippy::wildcard_imports, + clippy::let_unit_value, + )] + use tonic::codegen::*; + /// Generated trait containing gRPC methods that should be implemented for use with RepositoryServiceServer. + #[async_trait] + pub trait RepositoryService: std::marker::Send + std::marker::Sync + 'static { + async fn get_repository( + &self, + request: tonic::Request, + ) -> std::result::Result, tonic::Status>; + async fn init_repository( + &self, + request: tonic::Request, + ) -> std::result::Result, tonic::Status>; + async fn delete_repository( + &self, + request: tonic::Request, + ) -> std::result::Result, tonic::Status>; + async fn repository_exists( + &self, + request: tonic::Request, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + >; + async fn get_object_format( + &self, + request: tonic::Request, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + >; + async fn get_default_branch( + &self, + request: tonic::Request, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + >; + async fn set_default_branch( + &self, + request: tonic::Request, + ) -> std::result::Result, tonic::Status>; + async fn get_repository_config( + &self, + request: tonic::Request, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + >; + async fn set_repository_config( + &self, + request: tonic::Request, + ) -> std::result::Result, tonic::Status>; + async fn get_repository_statistics( + &self, + request: tonic::Request, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + >; + async fn check_repository_health( + &self, + request: tonic::Request, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + >; + async fn garbage_collect( + &self, + request: tonic::Request, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + >; + async fn repack( + &self, + request: tonic::Request, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + >; + async fn write_commit_graph( + &self, + request: tonic::Request, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + >; + } + #[derive(Debug)] + pub struct RepositoryServiceServer { + inner: Arc, + accept_compression_encodings: EnabledCompressionEncodings, + send_compression_encodings: EnabledCompressionEncodings, + max_decoding_message_size: Option, + max_encoding_message_size: Option, + } + impl RepositoryServiceServer { + pub fn new(inner: T) -> Self { + Self::from_arc(Arc::new(inner)) + } + pub fn from_arc(inner: Arc) -> Self { + Self { + inner, + accept_compression_encodings: Default::default(), + send_compression_encodings: Default::default(), + max_decoding_message_size: None, + max_encoding_message_size: None, + } + } + pub fn with_interceptor( + inner: T, + interceptor: F, + ) -> InterceptedService + where + F: tonic::service::Interceptor, + { + InterceptedService::new(Self::new(inner), interceptor) + } + /// Enable decompressing requests with the given encoding. + #[must_use] + pub fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self { + self.accept_compression_encodings.enable(encoding); + self + } + /// Compress responses with the given encoding, if the client supports it. + #[must_use] + pub fn send_compressed(mut self, encoding: CompressionEncoding) -> Self { + self.send_compression_encodings.enable(encoding); + self + } + /// Limits the maximum size of a decoded message. + /// + /// Default: `4MB` + #[must_use] + pub fn max_decoding_message_size(mut self, limit: usize) -> Self { + self.max_decoding_message_size = Some(limit); + self + } + /// Limits the maximum size of an encoded message. + /// + /// Default: `usize::MAX` + #[must_use] + pub fn max_encoding_message_size(mut self, limit: usize) -> Self { + self.max_encoding_message_size = Some(limit); + self + } + } + impl tonic::codegen::Service> for RepositoryServiceServer + where + T: RepositoryService, + B: Body + std::marker::Send + 'static, + B::Error: Into + std::marker::Send + 'static, + { + type Response = http::Response; + type Error = std::convert::Infallible; + type Future = BoxFuture; + fn poll_ready( + &mut self, + _cx: &mut Context<'_>, + ) -> Poll> { + Poll::Ready(Ok(())) + } + fn call(&mut self, req: http::Request) -> Self::Future { + match req.uri().path() { + "/gitks.RepositoryService/GetRepository" => { + #[allow(non_camel_case_types)] + struct GetRepositorySvc(pub Arc); + impl< + T: RepositoryService, + > tonic::server::UnaryService + for GetRepositorySvc { + type Response = super::Repository; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::get_repository(&inner, request) + .await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let method = GetRepositorySvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/gitks.RepositoryService/InitRepository" => { + #[allow(non_camel_case_types)] + struct InitRepositorySvc(pub Arc); + impl< + T: RepositoryService, + > tonic::server::UnaryService + for InitRepositorySvc { + type Response = super::Repository; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::init_repository(&inner, request) + .await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let method = InitRepositorySvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/gitks.RepositoryService/DeleteRepository" => { + #[allow(non_camel_case_types)] + struct DeleteRepositorySvc(pub Arc); + impl< + T: RepositoryService, + > tonic::server::UnaryService + for DeleteRepositorySvc { + type Response = (); + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::delete_repository(&inner, request) + .await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let method = DeleteRepositorySvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/gitks.RepositoryService/RepositoryExists" => { + #[allow(non_camel_case_types)] + struct RepositoryExistsSvc(pub Arc); + impl< + T: RepositoryService, + > tonic::server::UnaryService + for RepositoryExistsSvc { + type Response = super::RepositoryExistsResponse; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::repository_exists(&inner, request) + .await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let method = RepositoryExistsSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/gitks.RepositoryService/GetObjectFormat" => { + #[allow(non_camel_case_types)] + struct GetObjectFormatSvc(pub Arc); + impl< + T: RepositoryService, + > tonic::server::UnaryService + for GetObjectFormatSvc { + type Response = super::RepositoryObjectFormatResponse; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::get_object_format(&inner, request) + .await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let method = GetObjectFormatSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/gitks.RepositoryService/GetDefaultBranch" => { + #[allow(non_camel_case_types)] + struct GetDefaultBranchSvc(pub Arc); + impl< + T: RepositoryService, + > tonic::server::UnaryService + for GetDefaultBranchSvc { + type Response = super::GetDefaultBranchResponse; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::get_default_branch( + &inner, + request, + ) + .await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let method = GetDefaultBranchSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/gitks.RepositoryService/SetDefaultBranch" => { + #[allow(non_camel_case_types)] + struct SetDefaultBranchSvc(pub Arc); + impl< + T: RepositoryService, + > tonic::server::UnaryService + for SetDefaultBranchSvc { + type Response = (); + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::set_default_branch( + &inner, + request, + ) + .await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let method = SetDefaultBranchSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/gitks.RepositoryService/GetRepositoryConfig" => { + #[allow(non_camel_case_types)] + struct GetRepositoryConfigSvc(pub Arc); + impl< + T: RepositoryService, + > tonic::server::UnaryService + for GetRepositoryConfigSvc { + type Response = super::GetRepositoryConfigResponse; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::get_repository_config( + &inner, + request, + ) + .await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let method = GetRepositoryConfigSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/gitks.RepositoryService/SetRepositoryConfig" => { + #[allow(non_camel_case_types)] + struct SetRepositoryConfigSvc(pub Arc); + impl< + T: RepositoryService, + > tonic::server::UnaryService + for SetRepositoryConfigSvc { + type Response = (); + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::set_repository_config( + &inner, + request, + ) + .await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let method = SetRepositoryConfigSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/gitks.RepositoryService/GetRepositoryStatistics" => { + #[allow(non_camel_case_types)] + struct GetRepositoryStatisticsSvc(pub Arc); + impl< + T: RepositoryService, + > tonic::server::UnaryService + for GetRepositoryStatisticsSvc { + type Response = super::RepositoryStatistics; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::get_repository_statistics( + &inner, + request, + ) + .await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let method = GetRepositoryStatisticsSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/gitks.RepositoryService/CheckRepositoryHealth" => { + #[allow(non_camel_case_types)] + struct CheckRepositoryHealthSvc(pub Arc); + impl< + T: RepositoryService, + > tonic::server::UnaryService + for CheckRepositoryHealthSvc { + type Response = super::RepositoryHealthResponse; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::check_repository_health( + &inner, + request, + ) + .await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let method = CheckRepositoryHealthSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/gitks.RepositoryService/GarbageCollect" => { + #[allow(non_camel_case_types)] + struct GarbageCollectSvc(pub Arc); + impl< + T: RepositoryService, + > tonic::server::UnaryService + for GarbageCollectSvc { + type Response = super::RepositoryMaintenanceResponse; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::garbage_collect(&inner, request) + .await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let method = GarbageCollectSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/gitks.RepositoryService/Repack" => { + #[allow(non_camel_case_types)] + struct RepackSvc(pub Arc); + impl< + T: RepositoryService, + > tonic::server::UnaryService + for RepackSvc { + type Response = super::RepositoryMaintenanceResponse; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::repack(&inner, request).await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let method = RepackSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/gitks.RepositoryService/WriteCommitGraph" => { + #[allow(non_camel_case_types)] + struct WriteCommitGraphSvc(pub Arc); + impl< + T: RepositoryService, + > tonic::server::UnaryService + for WriteCommitGraphSvc { + type Response = super::RepositoryMaintenanceResponse; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::write_commit_graph( + &inner, + request, + ) + .await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let method = WriteCommitGraphSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + _ => { + Box::pin(async move { + let mut response = http::Response::new(empty_body()); + let headers = response.headers_mut(); + headers + .insert( + tonic::Status::GRPC_STATUS, + (tonic::Code::Unimplemented as i32).into(), + ); + headers + .insert( + http::header::CONTENT_TYPE, + tonic::metadata::GRPC_CONTENT_TYPE, + ); + Ok(response) + }) + } + } + } + } + impl Clone for RepositoryServiceServer { + fn clone(&self) -> Self { + let inner = self.inner.clone(); + Self { + inner, + accept_compression_encodings: self.accept_compression_encodings, + send_compression_encodings: self.send_compression_encodings, + max_decoding_message_size: self.max_decoding_message_size, + max_encoding_message_size: self.max_encoding_message_size, + } + } + } + /// Generated gRPC service name + pub const SERVICE_NAME: &str = "gitks.RepositoryService"; + impl tonic::server::NamedService for RepositoryServiceServer { + const NAME: &'static str = SERVICE_NAME; + } +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ArchiveOptions { + #[prost(enumeration = "archive_options::Format", tag = "1")] + pub format: i32, + #[prost(string, tag = "2")] + pub prefix: ::prost::alloc::string::String, + #[prost(string, repeated, tag = "3")] + pub pathspec: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, + #[prost(int32, tag = "4")] + pub compression_level: i32, + #[prost(bool, tag = "5")] + pub include_global_extended_pax_headers: bool, +} +/// Nested message and enum types in `ArchiveOptions`. +pub mod archive_options { + #[derive( + Clone, + Copy, + Debug, + PartialEq, + Eq, + Hash, + PartialOrd, + Ord, + ::prost::Enumeration + )] + #[repr(i32)] + pub enum Format { + ArchiveFormatUnspecified = 0, + ArchiveFormatTar = 1, + ArchiveFormatTarGz = 2, + ArchiveFormatTarBz2 = 3, + ArchiveFormatTarXz = 4, + ArchiveFormatZip = 5, + } + impl Format { + /// String value of the enum field names used in the ProtoBuf definition. + /// + /// The values are not transformed in any way and thus are considered stable + /// (if the ProtoBuf definition does not change) and safe for programmatic use. + pub fn as_str_name(&self) -> &'static str { + match self { + Self::ArchiveFormatUnspecified => "ARCHIVE_FORMAT_UNSPECIFIED", + Self::ArchiveFormatTar => "ARCHIVE_FORMAT_TAR", + Self::ArchiveFormatTarGz => "ARCHIVE_FORMAT_TAR_GZ", + Self::ArchiveFormatTarBz2 => "ARCHIVE_FORMAT_TAR_BZ2", + Self::ArchiveFormatTarXz => "ARCHIVE_FORMAT_TAR_XZ", + Self::ArchiveFormatZip => "ARCHIVE_FORMAT_ZIP", + } + } + /// Creates an enum from field names used in the ProtoBuf definition. + pub fn from_str_name(value: &str) -> ::core::option::Option { + match value { + "ARCHIVE_FORMAT_UNSPECIFIED" => Some(Self::ArchiveFormatUnspecified), + "ARCHIVE_FORMAT_TAR" => Some(Self::ArchiveFormatTar), + "ARCHIVE_FORMAT_TAR_GZ" => Some(Self::ArchiveFormatTarGz), + "ARCHIVE_FORMAT_TAR_BZ2" => Some(Self::ArchiveFormatTarBz2), + "ARCHIVE_FORMAT_TAR_XZ" => Some(Self::ArchiveFormatTarXz), + "ARCHIVE_FORMAT_ZIP" => Some(Self::ArchiveFormatZip), + _ => None, + } + } + } +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ArchiveRequest { + #[prost(message, optional, tag = "1")] + pub repository: ::core::option::Option, + #[prost(message, optional, tag = "2")] + pub treeish: ::core::option::Option, + #[prost(message, optional, tag = "3")] + pub options: ::core::option::Option, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ArchiveChunk { + #[prost(bytes = "vec", tag = "1")] + pub data: ::prost::alloc::vec::Vec, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ArchiveEntry { + #[prost(string, tag = "1")] + pub path: ::prost::alloc::string::String, + #[prost(message, optional, tag = "2")] + pub oid: ::core::option::Option, + #[prost(uint32, tag = "3")] + pub mode: u32, + #[prost(int64, tag = "4")] + pub size: i64, + #[prost(enumeration = "ObjectType", tag = "5")] + pub r#type: i32, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ListArchiveEntriesRequest { + #[prost(message, optional, tag = "1")] + pub repository: ::core::option::Option, + #[prost(message, optional, tag = "2")] + pub treeish: ::core::option::Option, + #[prost(string, repeated, tag = "3")] + pub pathspec: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, + #[prost(message, optional, tag = "4")] + pub pagination: ::core::option::Option, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ListArchiveEntriesResponse { + #[prost(message, repeated, tag = "1")] + pub entries: ::prost::alloc::vec::Vec, + #[prost(message, optional, tag = "2")] + pub page_info: ::core::option::Option, +} +/// Generated client implementations. +pub mod archive_service_client { + #![allow( + unused_variables, + dead_code, + missing_docs, + clippy::wildcard_imports, + clippy::let_unit_value, + )] + use tonic::codegen::*; + use tonic::codegen::http::Uri; + #[derive(Debug, Clone)] + pub struct ArchiveServiceClient { + inner: tonic::client::Grpc, + } + impl ArchiveServiceClient { + /// Attempt to create a new client by connecting to a given endpoint. + pub async fn connect(dst: D) -> Result + where + D: TryInto, + D::Error: Into, + { + let conn = tonic::transport::Endpoint::new(dst)?.connect().await?; + Ok(Self::new(conn)) + } + } + impl ArchiveServiceClient + where + T: tonic::client::GrpcService, + T::Error: Into, + T::ResponseBody: Body + std::marker::Send + 'static, + ::Error: Into + std::marker::Send, + { + pub fn new(inner: T) -> Self { + let inner = tonic::client::Grpc::new(inner); + Self { inner } + } + pub fn with_origin(inner: T, origin: Uri) -> Self { + let inner = tonic::client::Grpc::with_origin(inner, origin); + Self { inner } + } + pub fn with_interceptor( + inner: T, + interceptor: F, + ) -> ArchiveServiceClient> + where + F: tonic::service::Interceptor, + T::ResponseBody: Default, + T: tonic::codegen::Service< + http::Request, + Response = http::Response< + >::ResponseBody, + >, + >, + , + >>::Error: Into + std::marker::Send + std::marker::Sync, + { + ArchiveServiceClient::new(InterceptedService::new(inner, interceptor)) + } + /// Compress requests with the given encoding. + /// + /// This requires the server to support it otherwise it might respond with an + /// error. + #[must_use] + pub fn send_compressed(mut self, encoding: CompressionEncoding) -> Self { + self.inner = self.inner.send_compressed(encoding); + self + } + /// Enable decompressing responses. + #[must_use] + pub fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self { + self.inner = self.inner.accept_compressed(encoding); + self + } + /// Limits the maximum size of a decoded message. + /// + /// Default: `4MB` + #[must_use] + pub fn max_decoding_message_size(mut self, limit: usize) -> Self { + self.inner = self.inner.max_decoding_message_size(limit); + self + } + /// Limits the maximum size of an encoded message. + /// + /// Default: `usize::MAX` + #[must_use] + pub fn max_encoding_message_size(mut self, limit: usize) -> Self { + self.inner = self.inner.max_encoding_message_size(limit); + self + } + pub async fn get_archive( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result< + tonic::Response>, + tonic::Status, + > { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic::codec::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/gitks.ArchiveService/GetArchive", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert(GrpcMethod::new("gitks.ArchiveService", "GetArchive")); + self.inner.server_streaming(req, path, codec).await + } + pub async fn list_archive_entries( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + > { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic::codec::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/gitks.ArchiveService/ListArchiveEntries", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert(GrpcMethod::new("gitks.ArchiveService", "ListArchiveEntries")); + self.inner.unary(req, path, codec).await + } + } +} +/// Generated server implementations. +pub mod archive_service_server { + #![allow( + unused_variables, + dead_code, + missing_docs, + clippy::wildcard_imports, + clippy::let_unit_value, + )] + use tonic::codegen::*; + /// Generated trait containing gRPC methods that should be implemented for use with ArchiveServiceServer. + #[async_trait] + pub trait ArchiveService: std::marker::Send + std::marker::Sync + 'static { + /// Server streaming response type for the GetArchive method. + type GetArchiveStream: tonic::codegen::tokio_stream::Stream< + Item = std::result::Result, + > + + std::marker::Send + + 'static; + async fn get_archive( + &self, + request: tonic::Request, + ) -> std::result::Result, tonic::Status>; + async fn list_archive_entries( + &self, + request: tonic::Request, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + >; + } + #[derive(Debug)] + pub struct ArchiveServiceServer { + inner: Arc, + accept_compression_encodings: EnabledCompressionEncodings, + send_compression_encodings: EnabledCompressionEncodings, + max_decoding_message_size: Option, + max_encoding_message_size: Option, + } + impl ArchiveServiceServer { + pub fn new(inner: T) -> Self { + Self::from_arc(Arc::new(inner)) + } + pub fn from_arc(inner: Arc) -> Self { + Self { + inner, + accept_compression_encodings: Default::default(), + send_compression_encodings: Default::default(), + max_decoding_message_size: None, + max_encoding_message_size: None, + } + } + pub fn with_interceptor( + inner: T, + interceptor: F, + ) -> InterceptedService + where + F: tonic::service::Interceptor, + { + InterceptedService::new(Self::new(inner), interceptor) + } + /// Enable decompressing requests with the given encoding. + #[must_use] + pub fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self { + self.accept_compression_encodings.enable(encoding); + self + } + /// Compress responses with the given encoding, if the client supports it. + #[must_use] + pub fn send_compressed(mut self, encoding: CompressionEncoding) -> Self { + self.send_compression_encodings.enable(encoding); + self + } + /// Limits the maximum size of a decoded message. + /// + /// Default: `4MB` + #[must_use] + pub fn max_decoding_message_size(mut self, limit: usize) -> Self { + self.max_decoding_message_size = Some(limit); + self + } + /// Limits the maximum size of an encoded message. + /// + /// Default: `usize::MAX` + #[must_use] + pub fn max_encoding_message_size(mut self, limit: usize) -> Self { + self.max_encoding_message_size = Some(limit); + self + } + } + impl tonic::codegen::Service> for ArchiveServiceServer + where + T: ArchiveService, + B: Body + std::marker::Send + 'static, + B::Error: Into + std::marker::Send + 'static, + { + type Response = http::Response; + type Error = std::convert::Infallible; + type Future = BoxFuture; + fn poll_ready( + &mut self, + _cx: &mut Context<'_>, + ) -> Poll> { + Poll::Ready(Ok(())) + } + fn call(&mut self, req: http::Request) -> Self::Future { + match req.uri().path() { + "/gitks.ArchiveService/GetArchive" => { + #[allow(non_camel_case_types)] + struct GetArchiveSvc(pub Arc); + impl< + T: ArchiveService, + > tonic::server::ServerStreamingService + for GetArchiveSvc { + type Response = super::ArchiveChunk; + type ResponseStream = T::GetArchiveStream; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::get_archive(&inner, request).await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let method = GetArchiveSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.server_streaming(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/gitks.ArchiveService/ListArchiveEntries" => { + #[allow(non_camel_case_types)] + struct ListArchiveEntriesSvc(pub Arc); + impl< + T: ArchiveService, + > tonic::server::UnaryService + for ListArchiveEntriesSvc { + type Response = super::ListArchiveEntriesResponse; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::list_archive_entries(&inner, request) + .await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let method = ListArchiveEntriesSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + _ => { + Box::pin(async move { + let mut response = http::Response::new(empty_body()); + let headers = response.headers_mut(); + headers + .insert( + tonic::Status::GRPC_STATUS, + (tonic::Code::Unimplemented as i32).into(), + ); + headers + .insert( + http::header::CONTENT_TYPE, + tonic::metadata::GRPC_CONTENT_TYPE, + ); + Ok(response) + }) + } + } + } + } + impl Clone for ArchiveServiceServer { + fn clone(&self) -> Self { + let inner = self.inner.clone(); + Self { + inner, + accept_compression_encodings: self.accept_compression_encodings, + send_compression_encodings: self.send_compression_encodings, + max_decoding_message_size: self.max_decoding_message_size, + max_encoding_message_size: self.max_encoding_message_size, + } + } + } + /// Generated gRPC service name + pub const SERVICE_NAME: &str = "gitks.ArchiveService"; + impl tonic::server::NamedService for ArchiveServiceServer { + const NAME: &'static str = SERVICE_NAME; + } +} +/// Git identity attached to commits and tags. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct Identity { + #[prost(string, tag = "1")] + pub name: ::prost::alloc::string::String, + #[prost(string, tag = "2")] + pub email: ::prost::alloc::string::String, +} +/// Git signature with timestamp and timezone offset. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct Signature { + #[prost(message, optional, tag = "1")] + pub identity: ::core::option::Option, + #[prost(message, optional, tag = "2")] + pub when: ::core::option::Option<::prost_types::Timestamp>, + /// Offset in minutes east of UTC, as stored by git. + #[prost(int32, tag = "3")] + pub timezone_offset: i32, +} +/// Backward-compatible payload name used by earlier Rust structs. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct PayloadTagger { + #[prost(string, tag = "1")] + pub email: ::prost::alloc::string::String, + #[prost(string, tag = "2")] + pub name: ::prost::alloc::string::String, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct VerifiedSignature { + #[prost(bool, tag = "1")] + pub verified: bool, + #[prost(enumeration = "verified_signature::Reason", tag = "2")] + pub reason: i32, + #[prost(string, tag = "3")] + pub signature: ::prost::alloc::string::String, + #[prost(string, tag = "4")] + pub payload: ::prost::alloc::string::String, + #[prost(string, tag = "5")] + pub key_fingerprint: ::prost::alloc::string::String, + #[prost(string, tag = "6")] + pub signer: ::prost::alloc::string::String, +} +/// Nested message and enum types in `VerifiedSignature`. +pub mod verified_signature { + #[derive( + Clone, + Copy, + Debug, + PartialEq, + Eq, + Hash, + PartialOrd, + Ord, + ::prost::Enumeration + )] + #[repr(i32)] + pub enum Reason { + Unspecified = 0, + Valid = 1, + ExpiredKey = 2, + NotSigningKey = 3, + GpgverifyError = 4, + GpgverifyUnavailable = 5, + Unsigned = 6, + UnknownSignatureType = 7, + NoUser = 8, + UnverifiedEmail = 9, + BadEmail = 10, + UnknownKey = 11, + MalformedSignature = 12, + Invalid = 13, + } + impl Reason { + /// String value of the enum field names used in the ProtoBuf definition. + /// + /// The values are not transformed in any way and thus are considered stable + /// (if the ProtoBuf definition does not change) and safe for programmatic use. + pub fn as_str_name(&self) -> &'static str { + match self { + Self::Unspecified => "REASON_UNSPECIFIED", + Self::Valid => "REASON_VALID", + Self::ExpiredKey => "REASON_EXPIRED_KEY", + Self::NotSigningKey => "REASON_NOT_SIGNING_KEY", + Self::GpgverifyError => "REASON_GPGVERIFY_ERROR", + Self::GpgverifyUnavailable => "REASON_GPGVERIFY_UNAVAILABLE", + Self::Unsigned => "REASON_UNSIGNED", + Self::UnknownSignatureType => "REASON_UNKNOWN_SIGNATURE_TYPE", + Self::NoUser => "REASON_NO_USER", + Self::UnverifiedEmail => "REASON_UNVERIFIED_EMAIL", + Self::BadEmail => "REASON_BAD_EMAIL", + Self::UnknownKey => "REASON_UNKNOWN_KEY", + Self::MalformedSignature => "REASON_MALFORMED_SIGNATURE", + Self::Invalid => "REASON_INVALID", + } + } + /// Creates an enum from field names used in the ProtoBuf definition. + pub fn from_str_name(value: &str) -> ::core::option::Option { + match value { + "REASON_UNSPECIFIED" => Some(Self::Unspecified), + "REASON_VALID" => Some(Self::Valid), + "REASON_EXPIRED_KEY" => Some(Self::ExpiredKey), + "REASON_NOT_SIGNING_KEY" => Some(Self::NotSigningKey), + "REASON_GPGVERIFY_ERROR" => Some(Self::GpgverifyError), + "REASON_GPGVERIFY_UNAVAILABLE" => Some(Self::GpgverifyUnavailable), + "REASON_UNSIGNED" => Some(Self::Unsigned), + "REASON_UNKNOWN_SIGNATURE_TYPE" => Some(Self::UnknownSignatureType), + "REASON_NO_USER" => Some(Self::NoUser), + "REASON_UNVERIFIED_EMAIL" => Some(Self::UnverifiedEmail), + "REASON_BAD_EMAIL" => Some(Self::BadEmail), + "REASON_UNKNOWN_KEY" => Some(Self::UnknownKey), + "REASON_MALFORMED_SIGNATURE" => Some(Self::MalformedSignature), + "REASON_INVALID" => Some(Self::Invalid), + _ => None, + } + } + } +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct PayloadCommit { + #[prost(message, optional, tag = "1")] + pub author: ::core::option::Option, + #[prost(message, optional, tag = "2")] + pub committer: ::core::option::Option, + #[prost(message, optional, tag = "3")] + pub oid: ::core::option::Option, + #[prost(string, tag = "4")] + pub message: ::prost::alloc::string::String, + #[prost(message, repeated, tag = "5")] + pub parents: ::prost::alloc::vec::Vec, + #[prost(message, optional, tag = "6")] + pub tree: ::core::option::Option, + #[prost(message, optional, tag = "7")] + pub timestamp: ::core::option::Option<::prost_types::Timestamp>, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct CommitTrailer { + #[prost(string, tag = "1")] + pub key: ::prost::alloc::string::String, + #[prost(string, tag = "2")] + pub value: ::prost::alloc::string::String, + #[prost(bool, tag = "3")] + pub separator_present: bool, +} +#[derive(Clone, Copy, PartialEq, ::prost::Message)] +pub struct CommitStats { + #[prost(uint32, tag = "1")] + pub additions: u32, + #[prost(uint32, tag = "2")] + pub deletions: u32, + #[prost(uint32, tag = "3")] + pub changed_files: u32, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct Commit { + #[prost(message, optional, tag = "1")] + pub oid: ::core::option::Option, + #[prost(string, tag = "2")] + pub abbreviated_oid: ::prost::alloc::string::String, + #[prost(message, repeated, tag = "3")] + pub parent_oids: ::prost::alloc::vec::Vec, + #[prost(message, optional, tag = "4")] + pub tree_oid: ::core::option::Option, + #[prost(message, optional, tag = "5")] + pub author: ::core::option::Option, + #[prost(message, optional, tag = "6")] + pub committer: ::core::option::Option, + #[prost(string, tag = "7")] + pub subject: ::prost::alloc::string::String, + #[prost(string, tag = "8")] + pub body: ::prost::alloc::string::String, + #[prost(string, tag = "9")] + pub message: ::prost::alloc::string::String, + #[prost(message, repeated, tag = "10")] + pub trailers: ::prost::alloc::vec::Vec, + #[prost(message, optional, tag = "11")] + pub signature: ::core::option::Option, + #[prost(message, optional, tag = "12")] + pub stats: ::core::option::Option, + #[prost(message, optional, tag = "13")] + pub authored_at: ::core::option::Option<::prost_types::Timestamp>, + #[prost(message, optional, tag = "14")] + pub committed_at: ::core::option::Option<::prost_types::Timestamp>, + #[prost(bytes = "vec", tag = "15")] + pub raw: ::prost::alloc::vec::Vec, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ListCommitsRequest { + #[prost(message, optional, tag = "1")] + pub repository: ::core::option::Option, + #[prost(message, optional, tag = "2")] + pub revision: ::core::option::Option, + #[prost(string, tag = "3")] + pub path: ::prost::alloc::string::String, + #[prost(message, optional, tag = "4")] + pub since: ::core::option::Option<::prost_types::Timestamp>, + #[prost(message, optional, tag = "5")] + pub until: ::core::option::Option<::prost_types::Timestamp>, + #[prost(bool, tag = "6")] + pub first_parent: bool, + #[prost(bool, tag = "7")] + pub all: bool, + #[prost(bool, tag = "8")] + pub reverse: bool, + #[prost(uint32, tag = "9")] + pub max_parents: u32, + #[prost(uint32, tag = "10")] + pub min_parents: u32, + #[prost(message, optional, tag = "11")] + pub pagination: ::core::option::Option, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ListCommitsResponse { + #[prost(message, repeated, tag = "1")] + pub commits: ::prost::alloc::vec::Vec, + #[prost(message, optional, tag = "2")] + pub page_info: ::core::option::Option, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct GetCommitRequest { + #[prost(message, optional, tag = "1")] + pub repository: ::core::option::Option, + #[prost(message, optional, tag = "2")] + pub revision: ::core::option::Option, + #[prost(bool, tag = "3")] + pub include_stats: bool, + #[prost(bool, tag = "4")] + pub include_raw: bool, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct GetCommitAncestorsRequest { + #[prost(message, optional, tag = "1")] + pub repository: ::core::option::Option, + #[prost(message, optional, tag = "2")] + pub revision: ::core::option::Option, + #[prost(bool, tag = "3")] + pub first_parent: bool, + #[prost(message, optional, tag = "4")] + pub pagination: ::core::option::Option, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct GetCommitAncestorsResponse { + #[prost(message, repeated, tag = "1")] + pub commits: ::prost::alloc::vec::Vec, + #[prost(message, optional, tag = "2")] + pub page_info: ::core::option::Option, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct CreateCommitAction { + #[prost(enumeration = "create_commit_action::Action", tag = "1")] + pub action: i32, + #[prost(string, tag = "2")] + pub file_path: ::prost::alloc::string::String, + #[prost(string, tag = "3")] + pub previous_path: ::prost::alloc::string::String, + #[prost(bytes = "vec", tag = "4")] + pub content: ::prost::alloc::vec::Vec, + #[prost(string, tag = "5")] + pub encoding: ::prost::alloc::string::String, + #[prost(bool, tag = "6")] + pub executable: bool, + #[prost(message, optional, tag = "7")] + pub last_commit_oid: ::core::option::Option, +} +/// Nested message and enum types in `CreateCommitAction`. +pub mod create_commit_action { + #[derive( + Clone, + Copy, + Debug, + PartialEq, + Eq, + Hash, + PartialOrd, + Ord, + ::prost::Enumeration + )] + #[repr(i32)] + pub enum Action { + CreateCommitActionUnspecified = 0, + CreateCommitActionCreate = 1, + CreateCommitActionUpdate = 2, + CreateCommitActionDelete = 3, + CreateCommitActionMove = 4, + CreateCommitActionChmod = 5, + } + impl Action { + /// String value of the enum field names used in the ProtoBuf definition. + /// + /// The values are not transformed in any way and thus are considered stable + /// (if the ProtoBuf definition does not change) and safe for programmatic use. + pub fn as_str_name(&self) -> &'static str { + match self { + Self::CreateCommitActionUnspecified => "CREATE_COMMIT_ACTION_UNSPECIFIED", + Self::CreateCommitActionCreate => "CREATE_COMMIT_ACTION_CREATE", + Self::CreateCommitActionUpdate => "CREATE_COMMIT_ACTION_UPDATE", + Self::CreateCommitActionDelete => "CREATE_COMMIT_ACTION_DELETE", + Self::CreateCommitActionMove => "CREATE_COMMIT_ACTION_MOVE", + Self::CreateCommitActionChmod => "CREATE_COMMIT_ACTION_CHMOD", + } + } + /// Creates an enum from field names used in the ProtoBuf definition. + pub fn from_str_name(value: &str) -> ::core::option::Option { + match value { + "CREATE_COMMIT_ACTION_UNSPECIFIED" => { + Some(Self::CreateCommitActionUnspecified) + } + "CREATE_COMMIT_ACTION_CREATE" => Some(Self::CreateCommitActionCreate), + "CREATE_COMMIT_ACTION_UPDATE" => Some(Self::CreateCommitActionUpdate), + "CREATE_COMMIT_ACTION_DELETE" => Some(Self::CreateCommitActionDelete), + "CREATE_COMMIT_ACTION_MOVE" => Some(Self::CreateCommitActionMove), + "CREATE_COMMIT_ACTION_CHMOD" => Some(Self::CreateCommitActionChmod), + _ => None, + } + } + } +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct CreateCommitRequest { + #[prost(message, optional, tag = "1")] + pub repository: ::core::option::Option, + #[prost(string, tag = "2")] + pub branch: ::prost::alloc::string::String, + #[prost(string, tag = "3")] + pub message: ::prost::alloc::string::String, + #[prost(message, optional, tag = "4")] + pub author: ::core::option::Option, + #[prost(message, optional, tag = "5")] + pub committer: ::core::option::Option, + #[prost(message, repeated, tag = "6")] + pub actions: ::prost::alloc::vec::Vec, + #[prost(message, optional, tag = "7")] + pub start_revision: ::core::option::Option, + #[prost(bool, tag = "8")] + pub force: bool, + #[prost(message, repeated, tag = "9")] + pub trailers: ::prost::alloc::vec::Vec, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct CreateCommitResponse { + #[prost(message, optional, tag = "1")] + pub commit: ::core::option::Option, + #[prost(string, tag = "2")] + pub branch: ::prost::alloc::string::String, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct RevertCommitRequest { + #[prost(message, optional, tag = "1")] + pub repository: ::core::option::Option, + #[prost(message, optional, tag = "2")] + pub commit: ::core::option::Option, + #[prost(string, tag = "3")] + pub branch: ::prost::alloc::string::String, + #[prost(message, optional, tag = "4")] + pub committer: ::core::option::Option, + #[prost(string, tag = "5")] + pub message: ::prost::alloc::string::String, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct CherryPickCommitRequest { + #[prost(message, optional, tag = "1")] + pub repository: ::core::option::Option, + #[prost(message, optional, tag = "2")] + pub commit: ::core::option::Option, + #[prost(string, tag = "3")] + pub branch: ::prost::alloc::string::String, + #[prost(message, optional, tag = "4")] + pub committer: ::core::option::Option, + #[prost(string, tag = "5")] + pub message: ::prost::alloc::string::String, + #[prost(uint32, tag = "6")] + pub mainline: u32, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct CompareCommitsRequest { + #[prost(message, optional, tag = "1")] + pub repository: ::core::option::Option, + #[prost(message, optional, tag = "2")] + pub base: ::core::option::Option, + #[prost(message, optional, tag = "3")] + pub head: ::core::option::Option, + #[prost(bool, tag = "4")] + pub straight: bool, + #[prost(bool, tag = "5")] + pub first_parent: bool, + #[prost(message, optional, tag = "6")] + pub pagination: ::core::option::Option, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct CompareCommitsResponse { + #[prost(message, repeated, tag = "1")] + pub commits: ::prost::alloc::vec::Vec, + #[prost(message, optional, tag = "2")] + pub stats: ::core::option::Option, + #[prost(message, optional, tag = "3")] + pub page_info: ::core::option::Option, + #[prost(message, optional, tag = "4")] + pub merge_base: ::core::option::Option, +} +/// Generated client implementations. +pub mod commit_service_client { + #![allow( + unused_variables, + dead_code, + missing_docs, + clippy::wildcard_imports, + clippy::let_unit_value, + )] + use tonic::codegen::*; + use tonic::codegen::http::Uri; + #[derive(Debug, Clone)] + pub struct CommitServiceClient { + inner: tonic::client::Grpc, + } + impl CommitServiceClient { + /// Attempt to create a new client by connecting to a given endpoint. + pub async fn connect(dst: D) -> Result + where + D: TryInto, + D::Error: Into, + { + let conn = tonic::transport::Endpoint::new(dst)?.connect().await?; + Ok(Self::new(conn)) + } + } + impl CommitServiceClient + where + T: tonic::client::GrpcService, + T::Error: Into, + T::ResponseBody: Body + std::marker::Send + 'static, + ::Error: Into + std::marker::Send, + { + pub fn new(inner: T) -> Self { + let inner = tonic::client::Grpc::new(inner); + Self { inner } + } + pub fn with_origin(inner: T, origin: Uri) -> Self { + let inner = tonic::client::Grpc::with_origin(inner, origin); + Self { inner } + } + pub fn with_interceptor( + inner: T, + interceptor: F, + ) -> CommitServiceClient> + where + F: tonic::service::Interceptor, + T::ResponseBody: Default, + T: tonic::codegen::Service< + http::Request, + Response = http::Response< + >::ResponseBody, + >, + >, + , + >>::Error: Into + std::marker::Send + std::marker::Sync, + { + CommitServiceClient::new(InterceptedService::new(inner, interceptor)) + } + /// Compress requests with the given encoding. + /// + /// This requires the server to support it otherwise it might respond with an + /// error. + #[must_use] + pub fn send_compressed(mut self, encoding: CompressionEncoding) -> Self { + self.inner = self.inner.send_compressed(encoding); + self + } + /// Enable decompressing responses. + #[must_use] + pub fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self { + self.inner = self.inner.accept_compressed(encoding); + self + } + /// Limits the maximum size of a decoded message. + /// + /// Default: `4MB` + #[must_use] + pub fn max_decoding_message_size(mut self, limit: usize) -> Self { + self.inner = self.inner.max_decoding_message_size(limit); + self + } + /// Limits the maximum size of an encoded message. + /// + /// Default: `usize::MAX` + #[must_use] + pub fn max_encoding_message_size(mut self, limit: usize) -> Self { + self.inner = self.inner.max_encoding_message_size(limit); + self + } + pub async fn list_commits( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + > { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic::codec::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/gitks.CommitService/ListCommits", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert(GrpcMethod::new("gitks.CommitService", "ListCommits")); + self.inner.unary(req, path, codec).await + } + pub async fn get_commit( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result, tonic::Status> { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic::codec::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/gitks.CommitService/GetCommit", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert(GrpcMethod::new("gitks.CommitService", "GetCommit")); + self.inner.unary(req, path, codec).await + } + pub async fn get_commit_ancestors( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + > { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic::codec::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/gitks.CommitService/GetCommitAncestors", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert(GrpcMethod::new("gitks.CommitService", "GetCommitAncestors")); + self.inner.unary(req, path, codec).await + } + pub async fn create_commit( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + > { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic::codec::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/gitks.CommitService/CreateCommit", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert(GrpcMethod::new("gitks.CommitService", "CreateCommit")); + self.inner.unary(req, path, codec).await + } + pub async fn revert_commit( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + > { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic::codec::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/gitks.CommitService/RevertCommit", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert(GrpcMethod::new("gitks.CommitService", "RevertCommit")); + self.inner.unary(req, path, codec).await + } + pub async fn cherry_pick_commit( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + > { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic::codec::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/gitks.CommitService/CherryPickCommit", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert(GrpcMethod::new("gitks.CommitService", "CherryPickCommit")); + self.inner.unary(req, path, codec).await + } + pub async fn compare_commits( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + > { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic::codec::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/gitks.CommitService/CompareCommits", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert(GrpcMethod::new("gitks.CommitService", "CompareCommits")); + self.inner.unary(req, path, codec).await + } + } +} +/// Generated server implementations. +pub mod commit_service_server { + #![allow( + unused_variables, + dead_code, + missing_docs, + clippy::wildcard_imports, + clippy::let_unit_value, + )] + use tonic::codegen::*; + /// Generated trait containing gRPC methods that should be implemented for use with CommitServiceServer. + #[async_trait] + pub trait CommitService: std::marker::Send + std::marker::Sync + 'static { + async fn list_commits( + &self, + request: tonic::Request, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + >; + async fn get_commit( + &self, + request: tonic::Request, + ) -> std::result::Result, tonic::Status>; + async fn get_commit_ancestors( + &self, + request: tonic::Request, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + >; + async fn create_commit( + &self, + request: tonic::Request, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + >; + async fn revert_commit( + &self, + request: tonic::Request, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + >; + async fn cherry_pick_commit( + &self, + request: tonic::Request, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + >; + async fn compare_commits( + &self, + request: tonic::Request, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + >; + } + #[derive(Debug)] + pub struct CommitServiceServer { + inner: Arc, + accept_compression_encodings: EnabledCompressionEncodings, + send_compression_encodings: EnabledCompressionEncodings, + max_decoding_message_size: Option, + max_encoding_message_size: Option, + } + impl CommitServiceServer { + pub fn new(inner: T) -> Self { + Self::from_arc(Arc::new(inner)) + } + pub fn from_arc(inner: Arc) -> Self { + Self { + inner, + accept_compression_encodings: Default::default(), + send_compression_encodings: Default::default(), + max_decoding_message_size: None, + max_encoding_message_size: None, + } + } + pub fn with_interceptor( + inner: T, + interceptor: F, + ) -> InterceptedService + where + F: tonic::service::Interceptor, + { + InterceptedService::new(Self::new(inner), interceptor) + } + /// Enable decompressing requests with the given encoding. + #[must_use] + pub fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self { + self.accept_compression_encodings.enable(encoding); + self + } + /// Compress responses with the given encoding, if the client supports it. + #[must_use] + pub fn send_compressed(mut self, encoding: CompressionEncoding) -> Self { + self.send_compression_encodings.enable(encoding); + self + } + /// Limits the maximum size of a decoded message. + /// + /// Default: `4MB` + #[must_use] + pub fn max_decoding_message_size(mut self, limit: usize) -> Self { + self.max_decoding_message_size = Some(limit); + self + } + /// Limits the maximum size of an encoded message. + /// + /// Default: `usize::MAX` + #[must_use] + pub fn max_encoding_message_size(mut self, limit: usize) -> Self { + self.max_encoding_message_size = Some(limit); + self + } + } + impl tonic::codegen::Service> for CommitServiceServer + where + T: CommitService, + B: Body + std::marker::Send + 'static, + B::Error: Into + std::marker::Send + 'static, + { + type Response = http::Response; + type Error = std::convert::Infallible; + type Future = BoxFuture; + fn poll_ready( + &mut self, + _cx: &mut Context<'_>, + ) -> Poll> { + Poll::Ready(Ok(())) + } + fn call(&mut self, req: http::Request) -> Self::Future { + match req.uri().path() { + "/gitks.CommitService/ListCommits" => { + #[allow(non_camel_case_types)] + struct ListCommitsSvc(pub Arc); + impl< + T: CommitService, + > tonic::server::UnaryService + for ListCommitsSvc { + type Response = super::ListCommitsResponse; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::list_commits(&inner, request).await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let method = ListCommitsSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/gitks.CommitService/GetCommit" => { + #[allow(non_camel_case_types)] + struct GetCommitSvc(pub Arc); + impl< + T: CommitService, + > tonic::server::UnaryService + for GetCommitSvc { + type Response = super::Commit; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::get_commit(&inner, request).await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let method = GetCommitSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/gitks.CommitService/GetCommitAncestors" => { + #[allow(non_camel_case_types)] + struct GetCommitAncestorsSvc(pub Arc); + impl< + T: CommitService, + > tonic::server::UnaryService + for GetCommitAncestorsSvc { + type Response = super::GetCommitAncestorsResponse; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::get_commit_ancestors(&inner, request) + .await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let method = GetCommitAncestorsSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/gitks.CommitService/CreateCommit" => { + #[allow(non_camel_case_types)] + struct CreateCommitSvc(pub Arc); + impl< + T: CommitService, + > tonic::server::UnaryService + for CreateCommitSvc { + type Response = super::CreateCommitResponse; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::create_commit(&inner, request).await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let method = CreateCommitSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/gitks.CommitService/RevertCommit" => { + #[allow(non_camel_case_types)] + struct RevertCommitSvc(pub Arc); + impl< + T: CommitService, + > tonic::server::UnaryService + for RevertCommitSvc { + type Response = super::CreateCommitResponse; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::revert_commit(&inner, request).await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let method = RevertCommitSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/gitks.CommitService/CherryPickCommit" => { + #[allow(non_camel_case_types)] + struct CherryPickCommitSvc(pub Arc); + impl< + T: CommitService, + > tonic::server::UnaryService + for CherryPickCommitSvc { + type Response = super::CreateCommitResponse; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::cherry_pick_commit(&inner, request) + .await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let method = CherryPickCommitSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/gitks.CommitService/CompareCommits" => { + #[allow(non_camel_case_types)] + struct CompareCommitsSvc(pub Arc); + impl< + T: CommitService, + > tonic::server::UnaryService + for CompareCommitsSvc { + type Response = super::CompareCommitsResponse; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::compare_commits(&inner, request).await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let method = CompareCommitsSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + _ => { + Box::pin(async move { + let mut response = http::Response::new(empty_body()); + let headers = response.headers_mut(); + headers + .insert( + tonic::Status::GRPC_STATUS, + (tonic::Code::Unimplemented as i32).into(), + ); + headers + .insert( + http::header::CONTENT_TYPE, + tonic::metadata::GRPC_CONTENT_TYPE, + ); + Ok(response) + }) + } + } + } + } + impl Clone for CommitServiceServer { + fn clone(&self) -> Self { + let inner = self.inner.clone(); + Self { + inner, + accept_compression_encodings: self.accept_compression_encodings, + send_compression_encodings: self.send_compression_encodings, + max_decoding_message_size: self.max_decoding_message_size, + max_encoding_message_size: self.max_encoding_message_size, + } + } + } + /// Generated gRPC service name + pub const SERVICE_NAME: &str = "gitks.CommitService"; + impl tonic::server::NamedService for CommitServiceServer { + const NAME: &'static str = SERVICE_NAME; + } +} +#[derive(Clone, Copy, PartialEq, ::prost::Message)] +pub struct LineRange { + #[prost(uint32, tag = "1")] + pub start: u32, + #[prost(uint32, tag = "2")] + pub end: u32, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct BlameOptions { + #[prost(bool, tag = "1")] + pub detect_move: bool, + #[prost(bool, tag = "2")] + pub detect_copy: bool, + #[prost(uint32, tag = "3")] + pub score: u32, + #[prost(string, repeated, tag = "4")] + pub ignore_revisions: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct BlameRequest { + #[prost(message, optional, tag = "1")] + pub repository: ::core::option::Option, + #[prost(message, optional, tag = "2")] + pub revision: ::core::option::Option, + #[prost(string, tag = "3")] + pub path: ::prost::alloc::string::String, + #[prost(message, optional, tag = "4")] + pub range: ::core::option::Option, + #[prost(message, optional, tag = "5")] + pub options: ::core::option::Option, + #[prost(message, optional, tag = "6")] + pub pagination: ::core::option::Option, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct BlameLine { + #[prost(uint32, tag = "1")] + pub final_line: u32, + #[prost(uint32, tag = "2")] + pub original_line: u32, + #[prost(bytes = "vec", tag = "3")] + pub content: ::prost::alloc::vec::Vec, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct BlameHunk { + #[prost(message, optional, tag = "1")] + pub commit: ::core::option::Option, + #[prost(string, tag = "2")] + pub original_path: ::prost::alloc::string::String, + #[prost(string, tag = "3")] + pub final_path: ::prost::alloc::string::String, + #[prost(uint32, tag = "4")] + pub original_start_line: u32, + #[prost(uint32, tag = "5")] + pub final_start_line: u32, + #[prost(uint32, tag = "6")] + pub line_count: u32, + #[prost(bool, tag = "7")] + pub boundary: bool, + #[prost(message, repeated, tag = "8")] + pub lines: ::prost::alloc::vec::Vec, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct BlameResponse { + #[prost(message, repeated, tag = "1")] + pub hunks: ::prost::alloc::vec::Vec, + #[prost(message, optional, tag = "2")] + pub page_info: ::core::option::Option, + #[prost(bool, tag = "3")] + pub truncated: bool, +} +/// Generated client implementations. +pub mod blame_service_client { + #![allow( + unused_variables, + dead_code, + missing_docs, + clippy::wildcard_imports, + clippy::let_unit_value, + )] + use tonic::codegen::*; + use tonic::codegen::http::Uri; + #[derive(Debug, Clone)] + pub struct BlameServiceClient { + inner: tonic::client::Grpc, + } + impl BlameServiceClient { + /// Attempt to create a new client by connecting to a given endpoint. + pub async fn connect(dst: D) -> Result + where + D: TryInto, + D::Error: Into, + { + let conn = tonic::transport::Endpoint::new(dst)?.connect().await?; + Ok(Self::new(conn)) + } + } + impl BlameServiceClient + where + T: tonic::client::GrpcService, + T::Error: Into, + T::ResponseBody: Body + std::marker::Send + 'static, + ::Error: Into + std::marker::Send, + { + pub fn new(inner: T) -> Self { + let inner = tonic::client::Grpc::new(inner); + Self { inner } + } + pub fn with_origin(inner: T, origin: Uri) -> Self { + let inner = tonic::client::Grpc::with_origin(inner, origin); + Self { inner } + } + pub fn with_interceptor( + inner: T, + interceptor: F, + ) -> BlameServiceClient> + where + F: tonic::service::Interceptor, + T::ResponseBody: Default, + T: tonic::codegen::Service< + http::Request, + Response = http::Response< + >::ResponseBody, + >, + >, + , + >>::Error: Into + std::marker::Send + std::marker::Sync, + { + BlameServiceClient::new(InterceptedService::new(inner, interceptor)) + } + /// Compress requests with the given encoding. + /// + /// This requires the server to support it otherwise it might respond with an + /// error. + #[must_use] + pub fn send_compressed(mut self, encoding: CompressionEncoding) -> Self { + self.inner = self.inner.send_compressed(encoding); + self + } + /// Enable decompressing responses. + #[must_use] + pub fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self { + self.inner = self.inner.accept_compressed(encoding); + self + } + /// Limits the maximum size of a decoded message. + /// + /// Default: `4MB` + #[must_use] + pub fn max_decoding_message_size(mut self, limit: usize) -> Self { + self.inner = self.inner.max_decoding_message_size(limit); + self + } + /// Limits the maximum size of an encoded message. + /// + /// Default: `usize::MAX` + #[must_use] + pub fn max_encoding_message_size(mut self, limit: usize) -> Self { + self.inner = self.inner.max_encoding_message_size(limit); + self + } + pub async fn blame( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result, tonic::Status> { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic::codec::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static("/gitks.BlameService/Blame"); + let mut req = request.into_request(); + req.extensions_mut().insert(GrpcMethod::new("gitks.BlameService", "Blame")); + self.inner.unary(req, path, codec).await + } + pub async fn stream_blame( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result< + tonic::Response>, + tonic::Status, + > { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic::codec::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/gitks.BlameService/StreamBlame", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert(GrpcMethod::new("gitks.BlameService", "StreamBlame")); + self.inner.server_streaming(req, path, codec).await + } + } +} +/// Generated server implementations. +pub mod blame_service_server { + #![allow( + unused_variables, + dead_code, + missing_docs, + clippy::wildcard_imports, + clippy::let_unit_value, + )] + use tonic::codegen::*; + /// Generated trait containing gRPC methods that should be implemented for use with BlameServiceServer. + #[async_trait] + pub trait BlameService: std::marker::Send + std::marker::Sync + 'static { + async fn blame( + &self, + request: tonic::Request, + ) -> std::result::Result, tonic::Status>; + /// Server streaming response type for the StreamBlame method. + type StreamBlameStream: tonic::codegen::tokio_stream::Stream< + Item = std::result::Result, + > + + std::marker::Send + + 'static; + async fn stream_blame( + &self, + request: tonic::Request, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + >; + } + #[derive(Debug)] + pub struct BlameServiceServer { + inner: Arc, + accept_compression_encodings: EnabledCompressionEncodings, + send_compression_encodings: EnabledCompressionEncodings, + max_decoding_message_size: Option, + max_encoding_message_size: Option, + } + impl BlameServiceServer { + pub fn new(inner: T) -> Self { + Self::from_arc(Arc::new(inner)) + } + pub fn from_arc(inner: Arc) -> Self { + Self { + inner, + accept_compression_encodings: Default::default(), + send_compression_encodings: Default::default(), + max_decoding_message_size: None, + max_encoding_message_size: None, + } + } + pub fn with_interceptor( + inner: T, + interceptor: F, + ) -> InterceptedService + where + F: tonic::service::Interceptor, + { + InterceptedService::new(Self::new(inner), interceptor) + } + /// Enable decompressing requests with the given encoding. + #[must_use] + pub fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self { + self.accept_compression_encodings.enable(encoding); + self + } + /// Compress responses with the given encoding, if the client supports it. + #[must_use] + pub fn send_compressed(mut self, encoding: CompressionEncoding) -> Self { + self.send_compression_encodings.enable(encoding); + self + } + /// Limits the maximum size of a decoded message. + /// + /// Default: `4MB` + #[must_use] + pub fn max_decoding_message_size(mut self, limit: usize) -> Self { + self.max_decoding_message_size = Some(limit); + self + } + /// Limits the maximum size of an encoded message. + /// + /// Default: `usize::MAX` + #[must_use] + pub fn max_encoding_message_size(mut self, limit: usize) -> Self { + self.max_encoding_message_size = Some(limit); + self + } + } + impl tonic::codegen::Service> for BlameServiceServer + where + T: BlameService, + B: Body + std::marker::Send + 'static, + B::Error: Into + std::marker::Send + 'static, + { + type Response = http::Response; + type Error = std::convert::Infallible; + type Future = BoxFuture; + fn poll_ready( + &mut self, + _cx: &mut Context<'_>, + ) -> Poll> { + Poll::Ready(Ok(())) + } + fn call(&mut self, req: http::Request) -> Self::Future { + match req.uri().path() { + "/gitks.BlameService/Blame" => { + #[allow(non_camel_case_types)] + struct BlameSvc(pub Arc); + impl< + T: BlameService, + > tonic::server::UnaryService for BlameSvc { + type Response = super::BlameResponse; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::blame(&inner, request).await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let method = BlameSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/gitks.BlameService/StreamBlame" => { + #[allow(non_camel_case_types)] + struct StreamBlameSvc(pub Arc); + impl< + T: BlameService, + > tonic::server::ServerStreamingService + for StreamBlameSvc { + type Response = super::BlameHunk; + type ResponseStream = T::StreamBlameStream; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::stream_blame(&inner, request).await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let method = StreamBlameSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.server_streaming(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + _ => { + Box::pin(async move { + let mut response = http::Response::new(empty_body()); + let headers = response.headers_mut(); + headers + .insert( + tonic::Status::GRPC_STATUS, + (tonic::Code::Unimplemented as i32).into(), + ); + headers + .insert( + http::header::CONTENT_TYPE, + tonic::metadata::GRPC_CONTENT_TYPE, + ); + Ok(response) + }) + } + } + } + } + impl Clone for BlameServiceServer { + fn clone(&self) -> Self { + let inner = self.inner.clone(); + Self { + inner, + accept_compression_encodings: self.accept_compression_encodings, + send_compression_encodings: self.send_compression_encodings, + max_decoding_message_size: self.max_decoding_message_size, + max_encoding_message_size: self.max_encoding_message_size, + } + } + } + /// Generated gRPC service name + pub const SERVICE_NAME: &str = "gitks.BlameService"; + impl tonic::server::NamedService for BlameServiceServer { + const NAME: &'static str = SERVICE_NAME; + } +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct BranchUpstream { + #[prost(string, tag = "1")] + pub remote_name: ::prost::alloc::string::String, + #[prost(string, tag = "2")] + pub remote_url: ::prost::alloc::string::String, + #[prost(string, tag = "3")] + pub remote_branch_name: ::prost::alloc::string::String, + #[prost(string, tag = "4")] + pub local_branch_name: ::prost::alloc::string::String, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct Branch { + #[prost(string, tag = "1")] + pub name: ::prost::alloc::string::String, + #[prost(string, tag = "2")] + pub full_ref: ::prost::alloc::string::String, + #[prost(message, optional, tag = "3")] + pub target_oid: ::core::option::Option, + #[prost(message, optional, tag = "4")] + pub commit: ::core::option::Option, + #[prost(message, optional, tag = "5")] + pub upstream: ::core::option::Option, + #[prost(bool, tag = "6")] + pub is_default: bool, + #[prost(bool, tag = "7")] + pub is_head: bool, + #[prost(bool, tag = "8")] + pub is_merged: bool, + #[prost(bool, tag = "9")] + pub is_detached: bool, +} +/// Backward-compatible payload name used by earlier clients. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct PayloadBranch { + #[prost(message, optional, tag = "1")] + pub commit: ::core::option::Option, + #[prost(string, tag = "2")] + pub name: ::prost::alloc::string::String, + #[prost(message, optional, tag = "3")] + pub upstream: ::core::option::Option, + #[prost(bool, tag = "4")] + pub is_head: bool, +} +#[derive(Clone, Copy, PartialEq, ::prost::Message)] +pub struct RequestBranchInit {} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ListBranchesRequest { + #[prost(message, optional, tag = "1")] + pub repository: ::core::option::Option, + #[prost(string, tag = "2")] + pub pattern: ::prost::alloc::string::String, + #[prost(bool, tag = "3")] + pub merged_into_head: bool, + #[prost(bool, tag = "4")] + pub not_merged_into_head: bool, + #[prost(message, optional, tag = "5")] + pub pagination: ::core::option::Option, + #[prost(enumeration = "SortDirection", tag = "6")] + pub sort_direction: i32, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ListBranchesResponse { + #[prost(message, repeated, tag = "1")] + pub branches: ::prost::alloc::vec::Vec, + #[prost(message, optional, tag = "2")] + pub page_info: ::core::option::Option, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct GetBranchRequest { + #[prost(message, optional, tag = "1")] + pub repository: ::core::option::Option, + #[prost(string, tag = "2")] + pub name: ::prost::alloc::string::String, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct CreateBranchRequest { + #[prost(message, optional, tag = "1")] + pub repository: ::core::option::Option, + #[prost(string, tag = "2")] + pub name: ::prost::alloc::string::String, + #[prost(message, optional, tag = "3")] + pub start_point: ::core::option::Option, + #[prost(bool, tag = "4")] + pub force: bool, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct DeleteBranchRequest { + #[prost(message, optional, tag = "1")] + pub repository: ::core::option::Option, + #[prost(string, tag = "2")] + pub name: ::prost::alloc::string::String, + #[prost(bool, tag = "3")] + pub force: bool, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct RenameBranchRequest { + #[prost(message, optional, tag = "1")] + pub repository: ::core::option::Option, + #[prost(string, tag = "2")] + pub old_name: ::prost::alloc::string::String, + #[prost(string, tag = "3")] + pub new_name: ::prost::alloc::string::String, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct UpdateBranchTargetRequest { + #[prost(message, optional, tag = "1")] + pub repository: ::core::option::Option, + #[prost(string, tag = "2")] + pub name: ::prost::alloc::string::String, + #[prost(message, optional, tag = "3")] + pub expected_old_oid: ::core::option::Option, + #[prost(message, optional, tag = "4")] + pub new_oid: ::core::option::Option, + #[prost(bool, tag = "5")] + pub force: bool, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct SetBranchUpstreamRequest { + #[prost(message, optional, tag = "1")] + pub repository: ::core::option::Option, + #[prost(string, tag = "2")] + pub name: ::prost::alloc::string::String, + #[prost(message, optional, tag = "3")] + pub upstream: ::core::option::Option, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct CompareBranchRequest { + #[prost(message, optional, tag = "1")] + pub repository: ::core::option::Option, + #[prost(string, tag = "2")] + pub source_branch: ::prost::alloc::string::String, + #[prost(string, tag = "3")] + pub target_branch: ::prost::alloc::string::String, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct CompareBranchResponse { + #[prost(bool, tag = "1")] + pub ahead: bool, + #[prost(bool, tag = "2")] + pub behind: bool, + #[prost(uint32, tag = "3")] + pub ahead_by: u32, + #[prost(uint32, tag = "4")] + pub behind_by: u32, + #[prost(message, optional, tag = "5")] + pub merge_base: ::core::option::Option, +} +/// Generated client implementations. +pub mod branch_service_client { + #![allow( + unused_variables, + dead_code, + missing_docs, + clippy::wildcard_imports, + clippy::let_unit_value, + )] + use tonic::codegen::*; + use tonic::codegen::http::Uri; + #[derive(Debug, Clone)] + pub struct BranchServiceClient { + inner: tonic::client::Grpc, + } + impl BranchServiceClient { + /// Attempt to create a new client by connecting to a given endpoint. + pub async fn connect(dst: D) -> Result + where + D: TryInto, + D::Error: Into, + { + let conn = tonic::transport::Endpoint::new(dst)?.connect().await?; + Ok(Self::new(conn)) + } + } + impl BranchServiceClient + where + T: tonic::client::GrpcService, + T::Error: Into, + T::ResponseBody: Body + std::marker::Send + 'static, + ::Error: Into + std::marker::Send, + { + pub fn new(inner: T) -> Self { + let inner = tonic::client::Grpc::new(inner); + Self { inner } + } + pub fn with_origin(inner: T, origin: Uri) -> Self { + let inner = tonic::client::Grpc::with_origin(inner, origin); + Self { inner } + } + pub fn with_interceptor( + inner: T, + interceptor: F, + ) -> BranchServiceClient> + where + F: tonic::service::Interceptor, + T::ResponseBody: Default, + T: tonic::codegen::Service< + http::Request, + Response = http::Response< + >::ResponseBody, + >, + >, + , + >>::Error: Into + std::marker::Send + std::marker::Sync, + { + BranchServiceClient::new(InterceptedService::new(inner, interceptor)) + } + /// Compress requests with the given encoding. + /// + /// This requires the server to support it otherwise it might respond with an + /// error. + #[must_use] + pub fn send_compressed(mut self, encoding: CompressionEncoding) -> Self { + self.inner = self.inner.send_compressed(encoding); + self + } + /// Enable decompressing responses. + #[must_use] + pub fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self { + self.inner = self.inner.accept_compressed(encoding); + self + } + /// Limits the maximum size of a decoded message. + /// + /// Default: `4MB` + #[must_use] + pub fn max_decoding_message_size(mut self, limit: usize) -> Self { + self.inner = self.inner.max_decoding_message_size(limit); + self + } + /// Limits the maximum size of an encoded message. + /// + /// Default: `usize::MAX` + #[must_use] + pub fn max_encoding_message_size(mut self, limit: usize) -> Self { + self.inner = self.inner.max_encoding_message_size(limit); + self + } + pub async fn list_branches( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + > { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic::codec::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/gitks.BranchService/ListBranches", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert(GrpcMethod::new("gitks.BranchService", "ListBranches")); + self.inner.unary(req, path, codec).await + } + pub async fn get_branch( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result, tonic::Status> { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic::codec::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/gitks.BranchService/GetBranch", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert(GrpcMethod::new("gitks.BranchService", "GetBranch")); + self.inner.unary(req, path, codec).await + } + pub async fn create_branch( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result, tonic::Status> { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic::codec::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/gitks.BranchService/CreateBranch", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert(GrpcMethod::new("gitks.BranchService", "CreateBranch")); + self.inner.unary(req, path, codec).await + } + pub async fn delete_branch( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result, tonic::Status> { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic::codec::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/gitks.BranchService/DeleteBranch", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert(GrpcMethod::new("gitks.BranchService", "DeleteBranch")); + self.inner.unary(req, path, codec).await + } + pub async fn rename_branch( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result, tonic::Status> { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic::codec::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/gitks.BranchService/RenameBranch", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert(GrpcMethod::new("gitks.BranchService", "RenameBranch")); + self.inner.unary(req, path, codec).await + } + pub async fn update_branch_target( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result, tonic::Status> { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic::codec::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/gitks.BranchService/UpdateBranchTarget", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert(GrpcMethod::new("gitks.BranchService", "UpdateBranchTarget")); + self.inner.unary(req, path, codec).await + } + pub async fn set_branch_upstream( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result, tonic::Status> { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic::codec::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/gitks.BranchService/SetBranchUpstream", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert(GrpcMethod::new("gitks.BranchService", "SetBranchUpstream")); + self.inner.unary(req, path, codec).await + } + pub async fn compare_branch( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + > { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic::codec::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/gitks.BranchService/CompareBranch", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert(GrpcMethod::new("gitks.BranchService", "CompareBranch")); + self.inner.unary(req, path, codec).await + } + } +} +/// Generated server implementations. +pub mod branch_service_server { + #![allow( + unused_variables, + dead_code, + missing_docs, + clippy::wildcard_imports, + clippy::let_unit_value, + )] + use tonic::codegen::*; + /// Generated trait containing gRPC methods that should be implemented for use with BranchServiceServer. + #[async_trait] + pub trait BranchService: std::marker::Send + std::marker::Sync + 'static { + async fn list_branches( + &self, + request: tonic::Request, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + >; + async fn get_branch( + &self, + request: tonic::Request, + ) -> std::result::Result, tonic::Status>; + async fn create_branch( + &self, + request: tonic::Request, + ) -> std::result::Result, tonic::Status>; + async fn delete_branch( + &self, + request: tonic::Request, + ) -> std::result::Result, tonic::Status>; + async fn rename_branch( + &self, + request: tonic::Request, + ) -> std::result::Result, tonic::Status>; + async fn update_branch_target( + &self, + request: tonic::Request, + ) -> std::result::Result, tonic::Status>; + async fn set_branch_upstream( + &self, + request: tonic::Request, + ) -> std::result::Result, tonic::Status>; + async fn compare_branch( + &self, + request: tonic::Request, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + >; + } + #[derive(Debug)] + pub struct BranchServiceServer { + inner: Arc, + accept_compression_encodings: EnabledCompressionEncodings, + send_compression_encodings: EnabledCompressionEncodings, + max_decoding_message_size: Option, + max_encoding_message_size: Option, + } + impl BranchServiceServer { + pub fn new(inner: T) -> Self { + Self::from_arc(Arc::new(inner)) + } + pub fn from_arc(inner: Arc) -> Self { + Self { + inner, + accept_compression_encodings: Default::default(), + send_compression_encodings: Default::default(), + max_decoding_message_size: None, + max_encoding_message_size: None, + } + } + pub fn with_interceptor( + inner: T, + interceptor: F, + ) -> InterceptedService + where + F: tonic::service::Interceptor, + { + InterceptedService::new(Self::new(inner), interceptor) + } + /// Enable decompressing requests with the given encoding. + #[must_use] + pub fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self { + self.accept_compression_encodings.enable(encoding); + self + } + /// Compress responses with the given encoding, if the client supports it. + #[must_use] + pub fn send_compressed(mut self, encoding: CompressionEncoding) -> Self { + self.send_compression_encodings.enable(encoding); + self + } + /// Limits the maximum size of a decoded message. + /// + /// Default: `4MB` + #[must_use] + pub fn max_decoding_message_size(mut self, limit: usize) -> Self { + self.max_decoding_message_size = Some(limit); + self + } + /// Limits the maximum size of an encoded message. + /// + /// Default: `usize::MAX` + #[must_use] + pub fn max_encoding_message_size(mut self, limit: usize) -> Self { + self.max_encoding_message_size = Some(limit); + self + } + } + impl tonic::codegen::Service> for BranchServiceServer + where + T: BranchService, + B: Body + std::marker::Send + 'static, + B::Error: Into + std::marker::Send + 'static, + { + type Response = http::Response; + type Error = std::convert::Infallible; + type Future = BoxFuture; + fn poll_ready( + &mut self, + _cx: &mut Context<'_>, + ) -> Poll> { + Poll::Ready(Ok(())) + } + fn call(&mut self, req: http::Request) -> Self::Future { + match req.uri().path() { + "/gitks.BranchService/ListBranches" => { + #[allow(non_camel_case_types)] + struct ListBranchesSvc(pub Arc); + impl< + T: BranchService, + > tonic::server::UnaryService + for ListBranchesSvc { + type Response = super::ListBranchesResponse; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::list_branches(&inner, request).await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let method = ListBranchesSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/gitks.BranchService/GetBranch" => { + #[allow(non_camel_case_types)] + struct GetBranchSvc(pub Arc); + impl< + T: BranchService, + > tonic::server::UnaryService + for GetBranchSvc { + type Response = super::Branch; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::get_branch(&inner, request).await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let method = GetBranchSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/gitks.BranchService/CreateBranch" => { + #[allow(non_camel_case_types)] + struct CreateBranchSvc(pub Arc); + impl< + T: BranchService, + > tonic::server::UnaryService + for CreateBranchSvc { + type Response = super::Branch; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::create_branch(&inner, request).await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let method = CreateBranchSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/gitks.BranchService/DeleteBranch" => { + #[allow(non_camel_case_types)] + struct DeleteBranchSvc(pub Arc); + impl< + T: BranchService, + > tonic::server::UnaryService + for DeleteBranchSvc { + type Response = (); + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::delete_branch(&inner, request).await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let method = DeleteBranchSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/gitks.BranchService/RenameBranch" => { + #[allow(non_camel_case_types)] + struct RenameBranchSvc(pub Arc); + impl< + T: BranchService, + > tonic::server::UnaryService + for RenameBranchSvc { + type Response = super::Branch; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::rename_branch(&inner, request).await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let method = RenameBranchSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/gitks.BranchService/UpdateBranchTarget" => { + #[allow(non_camel_case_types)] + struct UpdateBranchTargetSvc(pub Arc); + impl< + T: BranchService, + > tonic::server::UnaryService + for UpdateBranchTargetSvc { + type Response = super::Branch; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::update_branch_target(&inner, request) + .await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let method = UpdateBranchTargetSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/gitks.BranchService/SetBranchUpstream" => { + #[allow(non_camel_case_types)] + struct SetBranchUpstreamSvc(pub Arc); + impl< + T: BranchService, + > tonic::server::UnaryService + for SetBranchUpstreamSvc { + type Response = super::Branch; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::set_branch_upstream(&inner, request) + .await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let method = SetBranchUpstreamSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/gitks.BranchService/CompareBranch" => { + #[allow(non_camel_case_types)] + struct CompareBranchSvc(pub Arc); + impl< + T: BranchService, + > tonic::server::UnaryService + for CompareBranchSvc { + type Response = super::CompareBranchResponse; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::compare_branch(&inner, request).await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let method = CompareBranchSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + _ => { + Box::pin(async move { + let mut response = http::Response::new(empty_body()); + let headers = response.headers_mut(); + headers + .insert( + tonic::Status::GRPC_STATUS, + (tonic::Code::Unimplemented as i32).into(), + ); + headers + .insert( + http::header::CONTENT_TYPE, + tonic::metadata::GRPC_CONTENT_TYPE, + ); + Ok(response) + }) + } + } + } + } + impl Clone for BranchServiceServer { + fn clone(&self) -> Self { + let inner = self.inner.clone(); + Self { + inner, + accept_compression_encodings: self.accept_compression_encodings, + send_compression_encodings: self.send_compression_encodings, + max_decoding_message_size: self.max_decoding_message_size, + max_encoding_message_size: self.max_encoding_message_size, + } + } + } + /// Generated gRPC service name + pub const SERVICE_NAME: &str = "gitks.BranchService"; + impl tonic::server::NamedService for BranchServiceServer { + const NAME: &'static str = SERVICE_NAME; + } +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct DiffOptions { + #[prost(bool, tag = "1")] + pub recursive: bool, + #[prost(bool, tag = "2")] + pub include_binary: bool, + #[prost(bool, tag = "3")] + pub include_patch: bool, + #[prost(bool, tag = "4")] + pub rename_detection: bool, + #[prost(bool, tag = "5")] + pub copy_detection: bool, + #[prost(uint32, tag = "6")] + pub context_lines: u32, + #[prost(string, repeated, tag = "7")] + pub pathspec: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, + #[prost(enumeration = "diff_options::WhitespaceMode", tag = "8")] + pub whitespace_mode: i32, + #[prost(uint64, tag = "9")] + pub max_files: u64, + #[prost(uint64, tag = "10")] + pub max_bytes: u64, +} +/// Nested message and enum types in `DiffOptions`. +pub mod diff_options { + #[derive( + Clone, + Copy, + Debug, + PartialEq, + Eq, + Hash, + PartialOrd, + Ord, + ::prost::Enumeration + )] + #[repr(i32)] + pub enum WhitespaceMode { + DiffWhitespaceModeUnspecified = 0, + DiffWhitespaceModeDefault = 1, + DiffWhitespaceModeIgnoreAll = 2, + DiffWhitespaceModeIgnoreChange = 3, + DiffWhitespaceModeIgnoreEol = 4, + } + impl WhitespaceMode { + /// String value of the enum field names used in the ProtoBuf definition. + /// + /// The values are not transformed in any way and thus are considered stable + /// (if the ProtoBuf definition does not change) and safe for programmatic use. + pub fn as_str_name(&self) -> &'static str { + match self { + Self::DiffWhitespaceModeUnspecified => "DIFF_WHITESPACE_MODE_UNSPECIFIED", + Self::DiffWhitespaceModeDefault => "DIFF_WHITESPACE_MODE_DEFAULT", + Self::DiffWhitespaceModeIgnoreAll => "DIFF_WHITESPACE_MODE_IGNORE_ALL", + Self::DiffWhitespaceModeIgnoreChange => { + "DIFF_WHITESPACE_MODE_IGNORE_CHANGE" + } + Self::DiffWhitespaceModeIgnoreEol => "DIFF_WHITESPACE_MODE_IGNORE_EOL", + } + } + /// Creates an enum from field names used in the ProtoBuf definition. + pub fn from_str_name(value: &str) -> ::core::option::Option { + match value { + "DIFF_WHITESPACE_MODE_UNSPECIFIED" => { + Some(Self::DiffWhitespaceModeUnspecified) + } + "DIFF_WHITESPACE_MODE_DEFAULT" => Some(Self::DiffWhitespaceModeDefault), + "DIFF_WHITESPACE_MODE_IGNORE_ALL" => { + Some(Self::DiffWhitespaceModeIgnoreAll) + } + "DIFF_WHITESPACE_MODE_IGNORE_CHANGE" => { + Some(Self::DiffWhitespaceModeIgnoreChange) + } + "DIFF_WHITESPACE_MODE_IGNORE_EOL" => { + Some(Self::DiffWhitespaceModeIgnoreEol) + } + _ => None, + } + } + } +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct DiffLine { + #[prost(enumeration = "diff_line::LineType", tag = "1")] + pub r#type: i32, + #[prost(int32, tag = "2")] + pub old_line: i32, + #[prost(int32, tag = "3")] + pub new_line: i32, + #[prost(bytes = "vec", tag = "4")] + pub content: ::prost::alloc::vec::Vec, + #[prost(bool, tag = "5")] + pub truncated: bool, +} +/// Nested message and enum types in `DiffLine`. +pub mod diff_line { + #[derive( + Clone, + Copy, + Debug, + PartialEq, + Eq, + Hash, + PartialOrd, + Ord, + ::prost::Enumeration + )] + #[repr(i32)] + pub enum LineType { + DiffLineTypeUnspecified = 0, + DiffLineTypeContext = 1, + DiffLineTypeAdded = 2, + DiffLineTypeDeleted = 3, + DiffLineTypeHunkHeader = 4, + DiffLineTypeNoNewline = 5, + } + impl LineType { + /// String value of the enum field names used in the ProtoBuf definition. + /// + /// The values are not transformed in any way and thus are considered stable + /// (if the ProtoBuf definition does not change) and safe for programmatic use. + pub fn as_str_name(&self) -> &'static str { + match self { + Self::DiffLineTypeUnspecified => "DIFF_LINE_TYPE_UNSPECIFIED", + Self::DiffLineTypeContext => "DIFF_LINE_TYPE_CONTEXT", + Self::DiffLineTypeAdded => "DIFF_LINE_TYPE_ADDED", + Self::DiffLineTypeDeleted => "DIFF_LINE_TYPE_DELETED", + Self::DiffLineTypeHunkHeader => "DIFF_LINE_TYPE_HUNK_HEADER", + Self::DiffLineTypeNoNewline => "DIFF_LINE_TYPE_NO_NEWLINE", + } + } + /// Creates an enum from field names used in the ProtoBuf definition. + pub fn from_str_name(value: &str) -> ::core::option::Option { + match value { + "DIFF_LINE_TYPE_UNSPECIFIED" => Some(Self::DiffLineTypeUnspecified), + "DIFF_LINE_TYPE_CONTEXT" => Some(Self::DiffLineTypeContext), + "DIFF_LINE_TYPE_ADDED" => Some(Self::DiffLineTypeAdded), + "DIFF_LINE_TYPE_DELETED" => Some(Self::DiffLineTypeDeleted), + "DIFF_LINE_TYPE_HUNK_HEADER" => Some(Self::DiffLineTypeHunkHeader), + "DIFF_LINE_TYPE_NO_NEWLINE" => Some(Self::DiffLineTypeNoNewline), + _ => None, + } + } + } +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct DiffHunk { + #[prost(string, tag = "1")] + pub header: ::prost::alloc::string::String, + #[prost(uint32, tag = "2")] + pub old_start: u32, + #[prost(uint32, tag = "3")] + pub old_lines: u32, + #[prost(uint32, tag = "4")] + pub new_start: u32, + #[prost(uint32, tag = "5")] + pub new_lines: u32, + #[prost(message, repeated, tag = "6")] + pub lines: ::prost::alloc::vec::Vec, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct DiffFile { + #[prost(string, tag = "1")] + pub old_path: ::prost::alloc::string::String, + #[prost(string, tag = "2")] + pub new_path: ::prost::alloc::string::String, + #[prost(message, optional, tag = "3")] + pub old_oid: ::core::option::Option, + #[prost(message, optional, tag = "4")] + pub new_oid: ::core::option::Option, + #[prost(uint32, tag = "5")] + pub old_mode: u32, + #[prost(uint32, tag = "6")] + pub new_mode: u32, + #[prost(enumeration = "diff_file::ChangeType", tag = "7")] + pub change_type: i32, + #[prost(bool, tag = "8")] + pub binary: bool, + #[prost(bool, tag = "9")] + pub too_large: bool, + #[prost(uint32, tag = "10")] + pub additions: u32, + #[prost(uint32, tag = "11")] + pub deletions: u32, + #[prost(message, repeated, tag = "12")] + pub hunks: ::prost::alloc::vec::Vec, + #[prost(bytes = "vec", tag = "13")] + pub patch: ::prost::alloc::vec::Vec, + #[prost(double, tag = "14")] + pub similarity: f64, +} +/// Nested message and enum types in `DiffFile`. +pub mod diff_file { + #[derive( + Clone, + Copy, + Debug, + PartialEq, + Eq, + Hash, + PartialOrd, + Ord, + ::prost::Enumeration + )] + #[repr(i32)] + pub enum ChangeType { + DiffFileChangeTypeUnspecified = 0, + DiffFileChangeTypeAdded = 1, + DiffFileChangeTypeModified = 2, + DiffFileChangeTypeDeleted = 3, + DiffFileChangeTypeRenamed = 4, + DiffFileChangeTypeCopied = 5, + DiffFileChangeTypeTypeChanged = 6, + DiffFileChangeTypeUnmerged = 7, + } + impl ChangeType { + /// String value of the enum field names used in the ProtoBuf definition. + /// + /// The values are not transformed in any way and thus are considered stable + /// (if the ProtoBuf definition does not change) and safe for programmatic use. + pub fn as_str_name(&self) -> &'static str { + match self { + Self::DiffFileChangeTypeUnspecified => { + "DIFF_FILE_CHANGE_TYPE_UNSPECIFIED" + } + Self::DiffFileChangeTypeAdded => "DIFF_FILE_CHANGE_TYPE_ADDED", + Self::DiffFileChangeTypeModified => "DIFF_FILE_CHANGE_TYPE_MODIFIED", + Self::DiffFileChangeTypeDeleted => "DIFF_FILE_CHANGE_TYPE_DELETED", + Self::DiffFileChangeTypeRenamed => "DIFF_FILE_CHANGE_TYPE_RENAMED", + Self::DiffFileChangeTypeCopied => "DIFF_FILE_CHANGE_TYPE_COPIED", + Self::DiffFileChangeTypeTypeChanged => { + "DIFF_FILE_CHANGE_TYPE_TYPE_CHANGED" + } + Self::DiffFileChangeTypeUnmerged => "DIFF_FILE_CHANGE_TYPE_UNMERGED", + } + } + /// Creates an enum from field names used in the ProtoBuf definition. + pub fn from_str_name(value: &str) -> ::core::option::Option { + match value { + "DIFF_FILE_CHANGE_TYPE_UNSPECIFIED" => { + Some(Self::DiffFileChangeTypeUnspecified) + } + "DIFF_FILE_CHANGE_TYPE_ADDED" => Some(Self::DiffFileChangeTypeAdded), + "DIFF_FILE_CHANGE_TYPE_MODIFIED" => { + Some(Self::DiffFileChangeTypeModified) + } + "DIFF_FILE_CHANGE_TYPE_DELETED" => Some(Self::DiffFileChangeTypeDeleted), + "DIFF_FILE_CHANGE_TYPE_RENAMED" => Some(Self::DiffFileChangeTypeRenamed), + "DIFF_FILE_CHANGE_TYPE_COPIED" => Some(Self::DiffFileChangeTypeCopied), + "DIFF_FILE_CHANGE_TYPE_TYPE_CHANGED" => { + Some(Self::DiffFileChangeTypeTypeChanged) + } + "DIFF_FILE_CHANGE_TYPE_UNMERGED" => { + Some(Self::DiffFileChangeTypeUnmerged) + } + _ => None, + } + } + } +} +#[derive(Clone, Copy, PartialEq, ::prost::Message)] +pub struct DiffStats { + #[prost(uint32, tag = "1")] + pub additions: u32, + #[prost(uint32, tag = "2")] + pub deletions: u32, + #[prost(uint32, tag = "3")] + pub changed_files: u32, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct Diff { + #[prost(message, repeated, tag = "1")] + pub files: ::prost::alloc::vec::Vec, + #[prost(message, optional, tag = "2")] + pub stats: ::core::option::Option, + #[prost(bool, tag = "3")] + pub overflow: bool, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct GetDiffRequest { + #[prost(message, optional, tag = "1")] + pub repository: ::core::option::Option, + #[prost(message, optional, tag = "2")] + pub base: ::core::option::Option, + #[prost(message, optional, tag = "3")] + pub head: ::core::option::Option, + #[prost(message, optional, tag = "4")] + pub options: ::core::option::Option, + #[prost(message, optional, tag = "5")] + pub pagination: ::core::option::Option, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct GetDiffResponse { + #[prost(message, repeated, tag = "1")] + pub files: ::prost::alloc::vec::Vec, + #[prost(message, optional, tag = "2")] + pub stats: ::core::option::Option, + #[prost(message, optional, tag = "3")] + pub page_info: ::core::option::Option, + #[prost(bool, tag = "4")] + pub overflow: bool, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct GetCommitDiffRequest { + #[prost(message, optional, tag = "1")] + pub repository: ::core::option::Option, + #[prost(message, optional, tag = "2")] + pub commit: ::core::option::Option, + #[prost(message, optional, tag = "3")] + pub options: ::core::option::Option, + #[prost(message, optional, tag = "4")] + pub pagination: ::core::option::Option, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct GetPatchRequest { + #[prost(message, optional, tag = "1")] + pub repository: ::core::option::Option, + #[prost(message, optional, tag = "2")] + pub base: ::core::option::Option, + #[prost(message, optional, tag = "3")] + pub head: ::core::option::Option, + #[prost(message, optional, tag = "4")] + pub options: ::core::option::Option, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct GetPatchResponse { + #[prost(bytes = "vec", tag = "1")] + pub data: ::prost::alloc::vec::Vec, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct GetDiffStatsRequest { + #[prost(message, optional, tag = "1")] + pub repository: ::core::option::Option, + #[prost(message, optional, tag = "2")] + pub base: ::core::option::Option, + #[prost(message, optional, tag = "3")] + pub head: ::core::option::Option, + #[prost(message, optional, tag = "4")] + pub options: ::core::option::Option, +} +/// Generated client implementations. +pub mod diff_service_client { + #![allow( + unused_variables, + dead_code, + missing_docs, + clippy::wildcard_imports, + clippy::let_unit_value, + )] + use tonic::codegen::*; + use tonic::codegen::http::Uri; + #[derive(Debug, Clone)] + pub struct DiffServiceClient { + inner: tonic::client::Grpc, + } + impl DiffServiceClient { + /// Attempt to create a new client by connecting to a given endpoint. + pub async fn connect(dst: D) -> Result + where + D: TryInto, + D::Error: Into, + { + let conn = tonic::transport::Endpoint::new(dst)?.connect().await?; + Ok(Self::new(conn)) + } + } + impl DiffServiceClient + where + T: tonic::client::GrpcService, + T::Error: Into, + T::ResponseBody: Body + std::marker::Send + 'static, + ::Error: Into + std::marker::Send, + { + pub fn new(inner: T) -> Self { + let inner = tonic::client::Grpc::new(inner); + Self { inner } + } + pub fn with_origin(inner: T, origin: Uri) -> Self { + let inner = tonic::client::Grpc::with_origin(inner, origin); + Self { inner } + } + pub fn with_interceptor( + inner: T, + interceptor: F, + ) -> DiffServiceClient> + where + F: tonic::service::Interceptor, + T::ResponseBody: Default, + T: tonic::codegen::Service< + http::Request, + Response = http::Response< + >::ResponseBody, + >, + >, + , + >>::Error: Into + std::marker::Send + std::marker::Sync, + { + DiffServiceClient::new(InterceptedService::new(inner, interceptor)) + } + /// Compress requests with the given encoding. + /// + /// This requires the server to support it otherwise it might respond with an + /// error. + #[must_use] + pub fn send_compressed(mut self, encoding: CompressionEncoding) -> Self { + self.inner = self.inner.send_compressed(encoding); + self + } + /// Enable decompressing responses. + #[must_use] + pub fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self { + self.inner = self.inner.accept_compressed(encoding); + self + } + /// Limits the maximum size of a decoded message. + /// + /// Default: `4MB` + #[must_use] + pub fn max_decoding_message_size(mut self, limit: usize) -> Self { + self.inner = self.inner.max_decoding_message_size(limit); + self + } + /// Limits the maximum size of an encoded message. + /// + /// Default: `usize::MAX` + #[must_use] + pub fn max_encoding_message_size(mut self, limit: usize) -> Self { + self.inner = self.inner.max_encoding_message_size(limit); + self + } + pub async fn get_diff( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + > { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic::codec::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/gitks.DiffService/GetDiff", + ); + let mut req = request.into_request(); + req.extensions_mut().insert(GrpcMethod::new("gitks.DiffService", "GetDiff")); + self.inner.unary(req, path, codec).await + } + pub async fn get_commit_diff( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + > { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic::codec::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/gitks.DiffService/GetCommitDiff", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert(GrpcMethod::new("gitks.DiffService", "GetCommitDiff")); + self.inner.unary(req, path, codec).await + } + pub async fn get_patch( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result< + tonic::Response>, + tonic::Status, + > { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic::codec::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/gitks.DiffService/GetPatch", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert(GrpcMethod::new("gitks.DiffService", "GetPatch")); + self.inner.server_streaming(req, path, codec).await + } + pub async fn get_diff_stats( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result, tonic::Status> { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic::codec::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/gitks.DiffService/GetDiffStats", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert(GrpcMethod::new("gitks.DiffService", "GetDiffStats")); + self.inner.unary(req, path, codec).await + } + } +} +/// Generated server implementations. +pub mod diff_service_server { + #![allow( + unused_variables, + dead_code, + missing_docs, + clippy::wildcard_imports, + clippy::let_unit_value, + )] + use tonic::codegen::*; + /// Generated trait containing gRPC methods that should be implemented for use with DiffServiceServer. + #[async_trait] + pub trait DiffService: std::marker::Send + std::marker::Sync + 'static { + async fn get_diff( + &self, + request: tonic::Request, + ) -> std::result::Result, tonic::Status>; + async fn get_commit_diff( + &self, + request: tonic::Request, + ) -> std::result::Result, tonic::Status>; + /// Server streaming response type for the GetPatch method. + type GetPatchStream: tonic::codegen::tokio_stream::Stream< + Item = std::result::Result, + > + + std::marker::Send + + 'static; + async fn get_patch( + &self, + request: tonic::Request, + ) -> std::result::Result, tonic::Status>; + async fn get_diff_stats( + &self, + request: tonic::Request, + ) -> std::result::Result, tonic::Status>; + } + #[derive(Debug)] + pub struct DiffServiceServer { + inner: Arc, + accept_compression_encodings: EnabledCompressionEncodings, + send_compression_encodings: EnabledCompressionEncodings, + max_decoding_message_size: Option, + max_encoding_message_size: Option, + } + impl DiffServiceServer { + pub fn new(inner: T) -> Self { + Self::from_arc(Arc::new(inner)) + } + pub fn from_arc(inner: Arc) -> Self { + Self { + inner, + accept_compression_encodings: Default::default(), + send_compression_encodings: Default::default(), + max_decoding_message_size: None, + max_encoding_message_size: None, + } + } + pub fn with_interceptor( + inner: T, + interceptor: F, + ) -> InterceptedService + where + F: tonic::service::Interceptor, + { + InterceptedService::new(Self::new(inner), interceptor) + } + /// Enable decompressing requests with the given encoding. + #[must_use] + pub fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self { + self.accept_compression_encodings.enable(encoding); + self + } + /// Compress responses with the given encoding, if the client supports it. + #[must_use] + pub fn send_compressed(mut self, encoding: CompressionEncoding) -> Self { + self.send_compression_encodings.enable(encoding); + self + } + /// Limits the maximum size of a decoded message. + /// + /// Default: `4MB` + #[must_use] + pub fn max_decoding_message_size(mut self, limit: usize) -> Self { + self.max_decoding_message_size = Some(limit); + self + } + /// Limits the maximum size of an encoded message. + /// + /// Default: `usize::MAX` + #[must_use] + pub fn max_encoding_message_size(mut self, limit: usize) -> Self { + self.max_encoding_message_size = Some(limit); + self + } + } + impl tonic::codegen::Service> for DiffServiceServer + where + T: DiffService, + B: Body + std::marker::Send + 'static, + B::Error: Into + std::marker::Send + 'static, + { + type Response = http::Response; + type Error = std::convert::Infallible; + type Future = BoxFuture; + fn poll_ready( + &mut self, + _cx: &mut Context<'_>, + ) -> Poll> { + Poll::Ready(Ok(())) + } + fn call(&mut self, req: http::Request) -> Self::Future { + match req.uri().path() { + "/gitks.DiffService/GetDiff" => { + #[allow(non_camel_case_types)] + struct GetDiffSvc(pub Arc); + impl< + T: DiffService, + > tonic::server::UnaryService + for GetDiffSvc { + type Response = super::GetDiffResponse; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::get_diff(&inner, request).await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let method = GetDiffSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/gitks.DiffService/GetCommitDiff" => { + #[allow(non_camel_case_types)] + struct GetCommitDiffSvc(pub Arc); + impl< + T: DiffService, + > tonic::server::UnaryService + for GetCommitDiffSvc { + type Response = super::GetDiffResponse; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::get_commit_diff(&inner, request).await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let method = GetCommitDiffSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/gitks.DiffService/GetPatch" => { + #[allow(non_camel_case_types)] + struct GetPatchSvc(pub Arc); + impl< + T: DiffService, + > tonic::server::ServerStreamingService + for GetPatchSvc { + type Response = super::GetPatchResponse; + type ResponseStream = T::GetPatchStream; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::get_patch(&inner, request).await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let method = GetPatchSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.server_streaming(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/gitks.DiffService/GetDiffStats" => { + #[allow(non_camel_case_types)] + struct GetDiffStatsSvc(pub Arc); + impl< + T: DiffService, + > tonic::server::UnaryService + for GetDiffStatsSvc { + type Response = super::DiffStats; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::get_diff_stats(&inner, request).await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let method = GetDiffStatsSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + _ => { + Box::pin(async move { + let mut response = http::Response::new(empty_body()); + let headers = response.headers_mut(); + headers + .insert( + tonic::Status::GRPC_STATUS, + (tonic::Code::Unimplemented as i32).into(), + ); + headers + .insert( + http::header::CONTENT_TYPE, + tonic::metadata::GRPC_CONTENT_TYPE, + ); + Ok(response) + }) + } + } + } + } + impl Clone for DiffServiceServer { + fn clone(&self) -> Self { + let inner = self.inner.clone(); + Self { + inner, + accept_compression_encodings: self.accept_compression_encodings, + send_compression_encodings: self.send_compression_encodings, + max_decoding_message_size: self.max_decoding_message_size, + max_encoding_message_size: self.max_encoding_message_size, + } + } + } + /// Generated gRPC service name + pub const SERVICE_NAME: &str = "gitks.DiffService"; + impl tonic::server::NamedService for DiffServiceServer { + const NAME: &'static str = SERVICE_NAME; + } +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct MergeOptions { + #[prost(enumeration = "merge_options::Strategy", tag = "1")] + pub strategy: i32, + #[prost(enumeration = "merge_options::FastForwardMode", tag = "2")] + pub fast_forward: i32, + #[prost(bool, tag = "3")] + pub squash: bool, + #[prost(bool, tag = "4")] + pub no_commit: bool, + #[prost(bool, tag = "5")] + pub allow_unrelated_histories: bool, + #[prost(string, repeated, tag = "6")] + pub strategy_options: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, +} +/// Nested message and enum types in `MergeOptions`. +pub mod merge_options { + #[derive( + Clone, + Copy, + Debug, + PartialEq, + Eq, + Hash, + PartialOrd, + Ord, + ::prost::Enumeration + )] + #[repr(i32)] + pub enum Strategy { + MergeStrategyUnspecified = 0, + MergeStrategyRecursive = 1, + MergeStrategyOrt = 2, + MergeStrategyResolve = 3, + MergeStrategyOctopus = 4, + MergeStrategyOurs = 5, + MergeStrategySubtree = 6, + } + impl Strategy { + /// String value of the enum field names used in the ProtoBuf definition. + /// + /// The values are not transformed in any way and thus are considered stable + /// (if the ProtoBuf definition does not change) and safe for programmatic use. + pub fn as_str_name(&self) -> &'static str { + match self { + Self::MergeStrategyUnspecified => "MERGE_STRATEGY_UNSPECIFIED", + Self::MergeStrategyRecursive => "MERGE_STRATEGY_RECURSIVE", + Self::MergeStrategyOrt => "MERGE_STRATEGY_ORT", + Self::MergeStrategyResolve => "MERGE_STRATEGY_RESOLVE", + Self::MergeStrategyOctopus => "MERGE_STRATEGY_OCTOPUS", + Self::MergeStrategyOurs => "MERGE_STRATEGY_OURS", + Self::MergeStrategySubtree => "MERGE_STRATEGY_SUBTREE", + } + } + /// Creates an enum from field names used in the ProtoBuf definition. + pub fn from_str_name(value: &str) -> ::core::option::Option { + match value { + "MERGE_STRATEGY_UNSPECIFIED" => Some(Self::MergeStrategyUnspecified), + "MERGE_STRATEGY_RECURSIVE" => Some(Self::MergeStrategyRecursive), + "MERGE_STRATEGY_ORT" => Some(Self::MergeStrategyOrt), + "MERGE_STRATEGY_RESOLVE" => Some(Self::MergeStrategyResolve), + "MERGE_STRATEGY_OCTOPUS" => Some(Self::MergeStrategyOctopus), + "MERGE_STRATEGY_OURS" => Some(Self::MergeStrategyOurs), + "MERGE_STRATEGY_SUBTREE" => Some(Self::MergeStrategySubtree), + _ => None, + } + } + } + #[derive( + Clone, + Copy, + Debug, + PartialEq, + Eq, + Hash, + PartialOrd, + Ord, + ::prost::Enumeration + )] + #[repr(i32)] + pub enum FastForwardMode { + MergeFastForwardModeUnspecified = 0, + MergeFastForwardModeAllowed = 1, + MergeFastForwardModeOnly = 2, + MergeFastForwardModeNoFf = 3, + } + impl FastForwardMode { + /// String value of the enum field names used in the ProtoBuf definition. + /// + /// The values are not transformed in any way and thus are considered stable + /// (if the ProtoBuf definition does not change) and safe for programmatic use. + pub fn as_str_name(&self) -> &'static str { + match self { + Self::MergeFastForwardModeUnspecified => { + "MERGE_FAST_FORWARD_MODE_UNSPECIFIED" + } + Self::MergeFastForwardModeAllowed => "MERGE_FAST_FORWARD_MODE_ALLOWED", + Self::MergeFastForwardModeOnly => "MERGE_FAST_FORWARD_MODE_ONLY", + Self::MergeFastForwardModeNoFf => "MERGE_FAST_FORWARD_MODE_NO_FF", + } + } + /// Creates an enum from field names used in the ProtoBuf definition. + pub fn from_str_name(value: &str) -> ::core::option::Option { + match value { + "MERGE_FAST_FORWARD_MODE_UNSPECIFIED" => { + Some(Self::MergeFastForwardModeUnspecified) + } + "MERGE_FAST_FORWARD_MODE_ALLOWED" => { + Some(Self::MergeFastForwardModeAllowed) + } + "MERGE_FAST_FORWARD_MODE_ONLY" => Some(Self::MergeFastForwardModeOnly), + "MERGE_FAST_FORWARD_MODE_NO_FF" => Some(Self::MergeFastForwardModeNoFf), + _ => None, + } + } + } +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct MergeConflictSection { + #[prost(string, tag = "1")] + pub label: ::prost::alloc::string::String, + #[prost(bytes = "vec", tag = "2")] + pub content: ::prost::alloc::vec::Vec, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct MergeConflict { + #[prost(string, tag = "1")] + pub path: ::prost::alloc::string::String, + #[prost(uint32, tag = "2")] + pub mode: u32, + #[prost(message, optional, tag = "3")] + pub base_oid: ::core::option::Option, + #[prost(message, optional, tag = "4")] + pub ours_oid: ::core::option::Option, + #[prost(message, optional, tag = "5")] + pub theirs_oid: ::core::option::Option, + #[prost(message, repeated, tag = "6")] + pub sections: ::prost::alloc::vec::Vec, + #[prost(bool, tag = "7")] + pub binary: bool, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct MergeResult { + #[prost(enumeration = "merge_result::Status", tag = "1")] + pub status: i32, + #[prost(message, optional, tag = "2")] + pub commit: ::core::option::Option, + #[prost(message, optional, tag = "3")] + pub merge_base: ::core::option::Option, + #[prost(message, repeated, tag = "4")] + pub conflicts: ::prost::alloc::vec::Vec, + #[prost(message, optional, tag = "5")] + pub stats: ::core::option::Option, + #[prost(string, tag = "6")] + pub message: ::prost::alloc::string::String, +} +/// Nested message and enum types in `MergeResult`. +pub mod merge_result { + #[derive( + Clone, + Copy, + Debug, + PartialEq, + Eq, + Hash, + PartialOrd, + Ord, + ::prost::Enumeration + )] + #[repr(i32)] + pub enum Status { + MergeResultStatusUnspecified = 0, + MergeResultStatusMerged = 1, + MergeResultStatusFastForward = 2, + MergeResultStatusAlreadyUpToDate = 3, + MergeResultStatusConflicts = 4, + MergeResultStatusAborted = 5, + } + impl Status { + /// String value of the enum field names used in the ProtoBuf definition. + /// + /// The values are not transformed in any way and thus are considered stable + /// (if the ProtoBuf definition does not change) and safe for programmatic use. + pub fn as_str_name(&self) -> &'static str { + match self { + Self::MergeResultStatusUnspecified => "MERGE_RESULT_STATUS_UNSPECIFIED", + Self::MergeResultStatusMerged => "MERGE_RESULT_STATUS_MERGED", + Self::MergeResultStatusFastForward => "MERGE_RESULT_STATUS_FAST_FORWARD", + Self::MergeResultStatusAlreadyUpToDate => { + "MERGE_RESULT_STATUS_ALREADY_UP_TO_DATE" + } + Self::MergeResultStatusConflicts => "MERGE_RESULT_STATUS_CONFLICTS", + Self::MergeResultStatusAborted => "MERGE_RESULT_STATUS_ABORTED", + } + } + /// Creates an enum from field names used in the ProtoBuf definition. + pub fn from_str_name(value: &str) -> ::core::option::Option { + match value { + "MERGE_RESULT_STATUS_UNSPECIFIED" => { + Some(Self::MergeResultStatusUnspecified) + } + "MERGE_RESULT_STATUS_MERGED" => Some(Self::MergeResultStatusMerged), + "MERGE_RESULT_STATUS_FAST_FORWARD" => { + Some(Self::MergeResultStatusFastForward) + } + "MERGE_RESULT_STATUS_ALREADY_UP_TO_DATE" => { + Some(Self::MergeResultStatusAlreadyUpToDate) + } + "MERGE_RESULT_STATUS_CONFLICTS" => Some(Self::MergeResultStatusConflicts), + "MERGE_RESULT_STATUS_ABORTED" => Some(Self::MergeResultStatusAborted), + _ => None, + } + } + } +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct MergeRequest { + #[prost(message, optional, tag = "1")] + pub repository: ::core::option::Option, + #[prost(string, tag = "2")] + pub target_branch: ::prost::alloc::string::String, + #[prost(message, optional, tag = "3")] + pub source: ::core::option::Option, + #[prost(message, optional, tag = "4")] + pub committer: ::core::option::Option, + #[prost(string, tag = "5")] + pub message: ::prost::alloc::string::String, + #[prost(message, optional, tag = "6")] + pub options: ::core::option::Option, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct CheckMergeRequest { + #[prost(message, optional, tag = "1")] + pub repository: ::core::option::Option, + #[prost(message, optional, tag = "2")] + pub target: ::core::option::Option, + #[prost(message, optional, tag = "3")] + pub source: ::core::option::Option, + #[prost(message, optional, tag = "4")] + pub options: ::core::option::Option, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ListMergeConflictsRequest { + #[prost(message, optional, tag = "1")] + pub repository: ::core::option::Option, + #[prost(message, optional, tag = "2")] + pub target: ::core::option::Option, + #[prost(message, optional, tag = "3")] + pub source: ::core::option::Option, + #[prost(message, optional, tag = "4")] + pub pagination: ::core::option::Option, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ListMergeConflictsResponse { + #[prost(message, repeated, tag = "1")] + pub conflicts: ::prost::alloc::vec::Vec, + #[prost(message, optional, tag = "2")] + pub page_info: ::core::option::Option, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ResolveMergeConflict { + #[prost(string, tag = "1")] + pub path: ::prost::alloc::string::String, + #[prost(bytes = "vec", tag = "2")] + pub content: ::prost::alloc::vec::Vec, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ResolveMergeConflictsRequest { + #[prost(message, optional, tag = "1")] + pub repository: ::core::option::Option, + #[prost(string, tag = "2")] + pub target_branch: ::prost::alloc::string::String, + #[prost(message, optional, tag = "3")] + pub source: ::core::option::Option, + #[prost(message, repeated, tag = "4")] + pub resolutions: ::prost::alloc::vec::Vec, + #[prost(message, optional, tag = "5")] + pub committer: ::core::option::Option, + #[prost(string, tag = "6")] + pub message: ::prost::alloc::string::String, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct RebaseRequest { + #[prost(message, optional, tag = "1")] + pub repository: ::core::option::Option, + #[prost(string, tag = "2")] + pub branch: ::prost::alloc::string::String, + #[prost(message, optional, tag = "3")] + pub upstream: ::core::option::Option, + #[prost(message, optional, tag = "4")] + pub committer: ::core::option::Option, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct RebaseResult { + #[prost(enumeration = "rebase_result::Status", tag = "1")] + pub status: i32, + #[prost(message, optional, tag = "2")] + pub head: ::core::option::Option, + #[prost(message, repeated, tag = "3")] + pub conflicts: ::prost::alloc::vec::Vec, +} +/// Nested message and enum types in `RebaseResult`. +pub mod rebase_result { + #[derive( + Clone, + Copy, + Debug, + PartialEq, + Eq, + Hash, + PartialOrd, + Ord, + ::prost::Enumeration + )] + #[repr(i32)] + pub enum Status { + RebaseResultStatusUnspecified = 0, + RebaseResultStatusRebased = 1, + RebaseResultStatusAlreadyUpToDate = 2, + RebaseResultStatusConflicts = 3, + RebaseResultStatusAborted = 4, + } + impl Status { + /// String value of the enum field names used in the ProtoBuf definition. + /// + /// The values are not transformed in any way and thus are considered stable + /// (if the ProtoBuf definition does not change) and safe for programmatic use. + pub fn as_str_name(&self) -> &'static str { + match self { + Self::RebaseResultStatusUnspecified => "REBASE_RESULT_STATUS_UNSPECIFIED", + Self::RebaseResultStatusRebased => "REBASE_RESULT_STATUS_REBASED", + Self::RebaseResultStatusAlreadyUpToDate => { + "REBASE_RESULT_STATUS_ALREADY_UP_TO_DATE" + } + Self::RebaseResultStatusConflicts => "REBASE_RESULT_STATUS_CONFLICTS", + Self::RebaseResultStatusAborted => "REBASE_RESULT_STATUS_ABORTED", + } + } + /// Creates an enum from field names used in the ProtoBuf definition. + pub fn from_str_name(value: &str) -> ::core::option::Option { + match value { + "REBASE_RESULT_STATUS_UNSPECIFIED" => { + Some(Self::RebaseResultStatusUnspecified) + } + "REBASE_RESULT_STATUS_REBASED" => Some(Self::RebaseResultStatusRebased), + "REBASE_RESULT_STATUS_ALREADY_UP_TO_DATE" => { + Some(Self::RebaseResultStatusAlreadyUpToDate) + } + "REBASE_RESULT_STATUS_CONFLICTS" => { + Some(Self::RebaseResultStatusConflicts) + } + "REBASE_RESULT_STATUS_ABORTED" => Some(Self::RebaseResultStatusAborted), + _ => None, + } + } + } +} +/// Generated client implementations. +pub mod merge_service_client { + #![allow( + unused_variables, + dead_code, + missing_docs, + clippy::wildcard_imports, + clippy::let_unit_value, + )] + use tonic::codegen::*; + use tonic::codegen::http::Uri; + #[derive(Debug, Clone)] + pub struct MergeServiceClient { + inner: tonic::client::Grpc, + } + impl MergeServiceClient { + /// Attempt to create a new client by connecting to a given endpoint. + pub async fn connect(dst: D) -> Result + where + D: TryInto, + D::Error: Into, + { + let conn = tonic::transport::Endpoint::new(dst)?.connect().await?; + Ok(Self::new(conn)) + } + } + impl MergeServiceClient + where + T: tonic::client::GrpcService, + T::Error: Into, + T::ResponseBody: Body + std::marker::Send + 'static, + ::Error: Into + std::marker::Send, + { + pub fn new(inner: T) -> Self { + let inner = tonic::client::Grpc::new(inner); + Self { inner } + } + pub fn with_origin(inner: T, origin: Uri) -> Self { + let inner = tonic::client::Grpc::with_origin(inner, origin); + Self { inner } + } + pub fn with_interceptor( + inner: T, + interceptor: F, + ) -> MergeServiceClient> + where + F: tonic::service::Interceptor, + T::ResponseBody: Default, + T: tonic::codegen::Service< + http::Request, + Response = http::Response< + >::ResponseBody, + >, + >, + , + >>::Error: Into + std::marker::Send + std::marker::Sync, + { + MergeServiceClient::new(InterceptedService::new(inner, interceptor)) + } + /// Compress requests with the given encoding. + /// + /// This requires the server to support it otherwise it might respond with an + /// error. + #[must_use] + pub fn send_compressed(mut self, encoding: CompressionEncoding) -> Self { + self.inner = self.inner.send_compressed(encoding); + self + } + /// Enable decompressing responses. + #[must_use] + pub fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self { + self.inner = self.inner.accept_compressed(encoding); + self + } + /// Limits the maximum size of a decoded message. + /// + /// Default: `4MB` + #[must_use] + pub fn max_decoding_message_size(mut self, limit: usize) -> Self { + self.inner = self.inner.max_decoding_message_size(limit); + self + } + /// Limits the maximum size of an encoded message. + /// + /// Default: `usize::MAX` + #[must_use] + pub fn max_encoding_message_size(mut self, limit: usize) -> Self { + self.inner = self.inner.max_encoding_message_size(limit); + self + } + pub async fn check_merge( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result, tonic::Status> { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic::codec::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/gitks.MergeService/CheckMerge", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert(GrpcMethod::new("gitks.MergeService", "CheckMerge")); + self.inner.unary(req, path, codec).await + } + pub async fn merge( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result, tonic::Status> { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic::codec::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static("/gitks.MergeService/Merge"); + let mut req = request.into_request(); + req.extensions_mut().insert(GrpcMethod::new("gitks.MergeService", "Merge")); + self.inner.unary(req, path, codec).await + } + pub async fn list_merge_conflicts( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + > { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic::codec::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/gitks.MergeService/ListMergeConflicts", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert(GrpcMethod::new("gitks.MergeService", "ListMergeConflicts")); + self.inner.unary(req, path, codec).await + } + pub async fn resolve_merge_conflicts( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result, tonic::Status> { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic::codec::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/gitks.MergeService/ResolveMergeConflicts", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert(GrpcMethod::new("gitks.MergeService", "ResolveMergeConflicts")); + self.inner.unary(req, path, codec).await + } + pub async fn rebase( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result, tonic::Status> { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic::codec::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/gitks.MergeService/Rebase", + ); + let mut req = request.into_request(); + req.extensions_mut().insert(GrpcMethod::new("gitks.MergeService", "Rebase")); + self.inner.unary(req, path, codec).await + } + } +} +/// Generated server implementations. +pub mod merge_service_server { + #![allow( + unused_variables, + dead_code, + missing_docs, + clippy::wildcard_imports, + clippy::let_unit_value, + )] + use tonic::codegen::*; + /// Generated trait containing gRPC methods that should be implemented for use with MergeServiceServer. + #[async_trait] + pub trait MergeService: std::marker::Send + std::marker::Sync + 'static { + async fn check_merge( + &self, + request: tonic::Request, + ) -> std::result::Result, tonic::Status>; + async fn merge( + &self, + request: tonic::Request, + ) -> std::result::Result, tonic::Status>; + async fn list_merge_conflicts( + &self, + request: tonic::Request, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + >; + async fn resolve_merge_conflicts( + &self, + request: tonic::Request, + ) -> std::result::Result, tonic::Status>; + async fn rebase( + &self, + request: tonic::Request, + ) -> std::result::Result, tonic::Status>; + } + #[derive(Debug)] + pub struct MergeServiceServer { + inner: Arc, + accept_compression_encodings: EnabledCompressionEncodings, + send_compression_encodings: EnabledCompressionEncodings, + max_decoding_message_size: Option, + max_encoding_message_size: Option, + } + impl MergeServiceServer { + pub fn new(inner: T) -> Self { + Self::from_arc(Arc::new(inner)) + } + pub fn from_arc(inner: Arc) -> Self { + Self { + inner, + accept_compression_encodings: Default::default(), + send_compression_encodings: Default::default(), + max_decoding_message_size: None, + max_encoding_message_size: None, + } + } + pub fn with_interceptor( + inner: T, + interceptor: F, + ) -> InterceptedService + where + F: tonic::service::Interceptor, + { + InterceptedService::new(Self::new(inner), interceptor) + } + /// Enable decompressing requests with the given encoding. + #[must_use] + pub fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self { + self.accept_compression_encodings.enable(encoding); + self + } + /// Compress responses with the given encoding, if the client supports it. + #[must_use] + pub fn send_compressed(mut self, encoding: CompressionEncoding) -> Self { + self.send_compression_encodings.enable(encoding); + self + } + /// Limits the maximum size of a decoded message. + /// + /// Default: `4MB` + #[must_use] + pub fn max_decoding_message_size(mut self, limit: usize) -> Self { + self.max_decoding_message_size = Some(limit); + self + } + /// Limits the maximum size of an encoded message. + /// + /// Default: `usize::MAX` + #[must_use] + pub fn max_encoding_message_size(mut self, limit: usize) -> Self { + self.max_encoding_message_size = Some(limit); + self + } + } + impl tonic::codegen::Service> for MergeServiceServer + where + T: MergeService, + B: Body + std::marker::Send + 'static, + B::Error: Into + std::marker::Send + 'static, + { + type Response = http::Response; + type Error = std::convert::Infallible; + type Future = BoxFuture; + fn poll_ready( + &mut self, + _cx: &mut Context<'_>, + ) -> Poll> { + Poll::Ready(Ok(())) + } + fn call(&mut self, req: http::Request) -> Self::Future { + match req.uri().path() { + "/gitks.MergeService/CheckMerge" => { + #[allow(non_camel_case_types)] + struct CheckMergeSvc(pub Arc); + impl< + T: MergeService, + > tonic::server::UnaryService + for CheckMergeSvc { + type Response = super::MergeResult; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::check_merge(&inner, request).await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let method = CheckMergeSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/gitks.MergeService/Merge" => { + #[allow(non_camel_case_types)] + struct MergeSvc(pub Arc); + impl< + T: MergeService, + > tonic::server::UnaryService for MergeSvc { + type Response = super::MergeResult; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::merge(&inner, request).await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let method = MergeSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/gitks.MergeService/ListMergeConflicts" => { + #[allow(non_camel_case_types)] + struct ListMergeConflictsSvc(pub Arc); + impl< + T: MergeService, + > tonic::server::UnaryService + for ListMergeConflictsSvc { + type Response = super::ListMergeConflictsResponse; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::list_merge_conflicts(&inner, request) + .await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let method = ListMergeConflictsSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/gitks.MergeService/ResolveMergeConflicts" => { + #[allow(non_camel_case_types)] + struct ResolveMergeConflictsSvc(pub Arc); + impl< + T: MergeService, + > tonic::server::UnaryService + for ResolveMergeConflictsSvc { + type Response = super::MergeResult; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::resolve_merge_conflicts( + &inner, + request, + ) + .await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let method = ResolveMergeConflictsSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/gitks.MergeService/Rebase" => { + #[allow(non_camel_case_types)] + struct RebaseSvc(pub Arc); + impl< + T: MergeService, + > tonic::server::UnaryService + for RebaseSvc { + type Response = super::RebaseResult; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::rebase(&inner, request).await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let method = RebaseSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + _ => { + Box::pin(async move { + let mut response = http::Response::new(empty_body()); + let headers = response.headers_mut(); + headers + .insert( + tonic::Status::GRPC_STATUS, + (tonic::Code::Unimplemented as i32).into(), + ); + headers + .insert( + http::header::CONTENT_TYPE, + tonic::metadata::GRPC_CONTENT_TYPE, + ); + Ok(response) + }) + } + } + } + } + impl Clone for MergeServiceServer { + fn clone(&self) -> Self { + let inner = self.inner.clone(); + Self { + inner, + accept_compression_encodings: self.accept_compression_encodings, + send_compression_encodings: self.send_compression_encodings, + max_decoding_message_size: self.max_decoding_message_size, + max_encoding_message_size: self.max_encoding_message_size, + } + } + } + /// Generated gRPC service name + pub const SERVICE_NAME: &str = "gitks.MergeService"; + impl tonic::server::NamedService for MergeServiceServer { + const NAME: &'static str = SERVICE_NAME; + } +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct GitProtocolFeatures { + #[prost(uint32, tag = "1")] + pub version: u32, + #[prost(string, repeated, tag = "2")] + pub capabilities: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, + #[prost(string, repeated, tag = "3")] + pub server_options: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, + #[prost(string, repeated, tag = "4")] + pub agent: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ReferenceAdvertisement { + #[prost(string, tag = "1")] + pub name: ::prost::alloc::string::String, + #[prost(message, optional, tag = "2")] + pub target_oid: ::core::option::Option, + #[prost(message, optional, tag = "3")] + pub peeled_oid: ::core::option::Option, + #[prost(bool, tag = "4")] + pub symbolic: bool, + #[prost(string, tag = "5")] + pub symbolic_target: ::prost::alloc::string::String, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct AdvertiseRefsRequest { + #[prost(message, optional, tag = "1")] + pub repository: ::core::option::Option, + #[prost(message, optional, tag = "2")] + pub protocol: ::core::option::Option, + #[prost(string, tag = "3")] + pub service: ::prost::alloc::string::String, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct AdvertiseRefsResponse { + #[prost(message, repeated, tag = "1")] + pub references: ::prost::alloc::vec::Vec, + #[prost(string, repeated, tag = "2")] + pub capabilities: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct UploadPackRequest { + #[prost(message, optional, tag = "1")] + pub repository: ::core::option::Option, + #[prost(message, optional, tag = "2")] + pub protocol: ::core::option::Option, + #[prost(bytes = "vec", tag = "3")] + pub packet: ::prost::alloc::vec::Vec, + #[prost(bool, tag = "4")] + pub done: bool, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct UploadPackResponse { + #[prost(bytes = "vec", tag = "1")] + pub packet: ::prost::alloc::vec::Vec, + #[prost(string, tag = "2")] + pub stderr: ::prost::alloc::string::String, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ReceivePackRequest { + #[prost(message, optional, tag = "1")] + pub repository: ::core::option::Option, + #[prost(message, optional, tag = "2")] + pub protocol: ::core::option::Option, + #[prost(bytes = "vec", tag = "3")] + pub packet: ::prost::alloc::vec::Vec, + #[prost(bool, tag = "4")] + pub done: bool, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ReceivePackResponse { + #[prost(bytes = "vec", tag = "1")] + pub packet: ::prost::alloc::vec::Vec, + #[prost(string, tag = "2")] + pub stderr: ::prost::alloc::string::String, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct PackObjectsOptions { + #[prost(message, repeated, tag = "1")] + pub wants: ::prost::alloc::vec::Vec, + #[prost(message, repeated, tag = "2")] + pub haves: ::prost::alloc::vec::Vec, + #[prost(string, repeated, tag = "3")] + pub shallow_revisions: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, + #[prost(uint32, tag = "4")] + pub deepen: u32, + #[prost(bool, tag = "5")] + pub thin_pack: bool, + #[prost(bool, tag = "6")] + pub include_tag: bool, + #[prost(bool, tag = "7")] + pub use_bitmaps: bool, + #[prost(bool, tag = "8")] + pub delta_base_offset: bool, + #[prost(string, repeated, tag = "9")] + pub pathspec: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct PackObjectsRequest { + #[prost(message, optional, tag = "1")] + pub repository: ::core::option::Option, + #[prost(message, optional, tag = "2")] + pub options: ::core::option::Option, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct PackfileChunk { + #[prost(bytes = "vec", tag = "1")] + pub data: ::prost::alloc::vec::Vec, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct IndexPackRequest { + #[prost(message, optional, tag = "1")] + pub repository: ::core::option::Option, + #[prost(bytes = "vec", tag = "2")] + pub data: ::prost::alloc::vec::Vec, + #[prost(bool, tag = "3")] + pub done: bool, + #[prost(bool, tag = "4")] + pub keep: bool, + #[prost(bool, tag = "5")] + pub strict: bool, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct IndexPackResponse { + #[prost(message, optional, tag = "1")] + pub pack_hash: ::core::option::Option, + #[prost(uint64, tag = "2")] + pub object_count: u64, + #[prost(string, tag = "3")] + pub stderr: ::prost::alloc::string::String, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ListPackfilesRequest { + #[prost(message, optional, tag = "1")] + pub repository: ::core::option::Option, + #[prost(message, optional, tag = "2")] + pub pagination: ::core::option::Option, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct PackfileInfo { + #[prost(string, tag = "1")] + pub name: ::prost::alloc::string::String, + #[prost(message, optional, tag = "2")] + pub pack_hash: ::core::option::Option, + #[prost(uint64, tag = "3")] + pub size_bytes: u64, + #[prost(uint64, tag = "4")] + pub index_size_bytes: u64, + #[prost(uint64, tag = "5")] + pub object_count: u64, + #[prost(bool, tag = "6")] + pub has_bitmap: bool, + #[prost(bool, tag = "7")] + pub has_rev_index: bool, + #[prost(bool, tag = "8")] + pub kept: bool, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ListPackfilesResponse { + #[prost(message, repeated, tag = "1")] + pub packfiles: ::prost::alloc::vec::Vec, + #[prost(message, optional, tag = "2")] + pub page_info: ::core::option::Option, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct FsckRequest { + #[prost(message, optional, tag = "1")] + pub repository: ::core::option::Option, + #[prost(bool, tag = "2")] + pub strict: bool, + #[prost(bool, tag = "3")] + pub connectivity_only: bool, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct FsckResponse { + #[prost(bool, tag = "1")] + pub ok: bool, + #[prost(string, repeated, tag = "2")] + pub errors: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, + #[prost(string, repeated, tag = "3")] + pub warnings: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, +} +/// Generated client implementations. +pub mod pack_service_client { + #![allow( + unused_variables, + dead_code, + missing_docs, + clippy::wildcard_imports, + clippy::let_unit_value, + )] + use tonic::codegen::*; + use tonic::codegen::http::Uri; + #[derive(Debug, Clone)] + pub struct PackServiceClient { + inner: tonic::client::Grpc, + } + impl PackServiceClient { + /// Attempt to create a new client by connecting to a given endpoint. + pub async fn connect(dst: D) -> Result + where + D: TryInto, + D::Error: Into, + { + let conn = tonic::transport::Endpoint::new(dst)?.connect().await?; + Ok(Self::new(conn)) + } + } + impl PackServiceClient + where + T: tonic::client::GrpcService, + T::Error: Into, + T::ResponseBody: Body + std::marker::Send + 'static, + ::Error: Into + std::marker::Send, + { + pub fn new(inner: T) -> Self { + let inner = tonic::client::Grpc::new(inner); + Self { inner } + } + pub fn with_origin(inner: T, origin: Uri) -> Self { + let inner = tonic::client::Grpc::with_origin(inner, origin); + Self { inner } + } + pub fn with_interceptor( + inner: T, + interceptor: F, + ) -> PackServiceClient> + where + F: tonic::service::Interceptor, + T::ResponseBody: Default, + T: tonic::codegen::Service< + http::Request, + Response = http::Response< + >::ResponseBody, + >, + >, + , + >>::Error: Into + std::marker::Send + std::marker::Sync, + { + PackServiceClient::new(InterceptedService::new(inner, interceptor)) + } + /// Compress requests with the given encoding. + /// + /// This requires the server to support it otherwise it might respond with an + /// error. + #[must_use] + pub fn send_compressed(mut self, encoding: CompressionEncoding) -> Self { + self.inner = self.inner.send_compressed(encoding); + self + } + /// Enable decompressing responses. + #[must_use] + pub fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self { + self.inner = self.inner.accept_compressed(encoding); + self + } + /// Limits the maximum size of a decoded message. + /// + /// Default: `4MB` + #[must_use] + pub fn max_decoding_message_size(mut self, limit: usize) -> Self { + self.inner = self.inner.max_decoding_message_size(limit); + self + } + /// Limits the maximum size of an encoded message. + /// + /// Default: `usize::MAX` + #[must_use] + pub fn max_encoding_message_size(mut self, limit: usize) -> Self { + self.inner = self.inner.max_encoding_message_size(limit); + self + } + pub async fn advertise_refs( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + > { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic::codec::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/gitks.PackService/AdvertiseRefs", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert(GrpcMethod::new("gitks.PackService", "AdvertiseRefs")); + self.inner.unary(req, path, codec).await + } + pub async fn upload_pack( + &mut self, + request: impl tonic::IntoStreamingRequest, + ) -> std::result::Result< + tonic::Response>, + tonic::Status, + > { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic::codec::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/gitks.PackService/UploadPack", + ); + let mut req = request.into_streaming_request(); + req.extensions_mut() + .insert(GrpcMethod::new("gitks.PackService", "UploadPack")); + self.inner.streaming(req, path, codec).await + } + pub async fn receive_pack( + &mut self, + request: impl tonic::IntoStreamingRequest< + Message = super::ReceivePackRequest, + >, + ) -> std::result::Result< + tonic::Response>, + tonic::Status, + > { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic::codec::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/gitks.PackService/ReceivePack", + ); + let mut req = request.into_streaming_request(); + req.extensions_mut() + .insert(GrpcMethod::new("gitks.PackService", "ReceivePack")); + self.inner.streaming(req, path, codec).await + } + pub async fn pack_objects( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result< + tonic::Response>, + tonic::Status, + > { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic::codec::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/gitks.PackService/PackObjects", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert(GrpcMethod::new("gitks.PackService", "PackObjects")); + self.inner.server_streaming(req, path, codec).await + } + pub async fn index_pack( + &mut self, + request: impl tonic::IntoStreamingRequest, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + > { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic::codec::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/gitks.PackService/IndexPack", + ); + let mut req = request.into_streaming_request(); + req.extensions_mut() + .insert(GrpcMethod::new("gitks.PackService", "IndexPack")); + self.inner.client_streaming(req, path, codec).await + } + pub async fn list_packfiles( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + > { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic::codec::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/gitks.PackService/ListPackfiles", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert(GrpcMethod::new("gitks.PackService", "ListPackfiles")); + self.inner.unary(req, path, codec).await + } + pub async fn fsck( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result, tonic::Status> { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic::codec::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static("/gitks.PackService/Fsck"); + let mut req = request.into_request(); + req.extensions_mut().insert(GrpcMethod::new("gitks.PackService", "Fsck")); + self.inner.unary(req, path, codec).await + } + } +} +/// Generated server implementations. +pub mod pack_service_server { + #![allow( + unused_variables, + dead_code, + missing_docs, + clippy::wildcard_imports, + clippy::let_unit_value, + )] + use tonic::codegen::*; + /// Generated trait containing gRPC methods that should be implemented for use with PackServiceServer. + #[async_trait] + pub trait PackService: std::marker::Send + std::marker::Sync + 'static { + async fn advertise_refs( + &self, + request: tonic::Request, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + >; + /// Server streaming response type for the UploadPack method. + type UploadPackStream: tonic::codegen::tokio_stream::Stream< + Item = std::result::Result, + > + + std::marker::Send + + 'static; + async fn upload_pack( + &self, + request: tonic::Request>, + ) -> std::result::Result, tonic::Status>; + /// Server streaming response type for the ReceivePack method. + type ReceivePackStream: tonic::codegen::tokio_stream::Stream< + Item = std::result::Result, + > + + std::marker::Send + + 'static; + async fn receive_pack( + &self, + request: tonic::Request>, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + >; + /// Server streaming response type for the PackObjects method. + type PackObjectsStream: tonic::codegen::tokio_stream::Stream< + Item = std::result::Result, + > + + std::marker::Send + + 'static; + async fn pack_objects( + &self, + request: tonic::Request, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + >; + async fn index_pack( + &self, + request: tonic::Request>, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + >; + async fn list_packfiles( + &self, + request: tonic::Request, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + >; + async fn fsck( + &self, + request: tonic::Request, + ) -> std::result::Result, tonic::Status>; + } + #[derive(Debug)] + pub struct PackServiceServer { + inner: Arc, + accept_compression_encodings: EnabledCompressionEncodings, + send_compression_encodings: EnabledCompressionEncodings, + max_decoding_message_size: Option, + max_encoding_message_size: Option, + } + impl PackServiceServer { + pub fn new(inner: T) -> Self { + Self::from_arc(Arc::new(inner)) + } + pub fn from_arc(inner: Arc) -> Self { + Self { + inner, + accept_compression_encodings: Default::default(), + send_compression_encodings: Default::default(), + max_decoding_message_size: None, + max_encoding_message_size: None, + } + } + pub fn with_interceptor( + inner: T, + interceptor: F, + ) -> InterceptedService + where + F: tonic::service::Interceptor, + { + InterceptedService::new(Self::new(inner), interceptor) + } + /// Enable decompressing requests with the given encoding. + #[must_use] + pub fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self { + self.accept_compression_encodings.enable(encoding); + self + } + /// Compress responses with the given encoding, if the client supports it. + #[must_use] + pub fn send_compressed(mut self, encoding: CompressionEncoding) -> Self { + self.send_compression_encodings.enable(encoding); + self + } + /// Limits the maximum size of a decoded message. + /// + /// Default: `4MB` + #[must_use] + pub fn max_decoding_message_size(mut self, limit: usize) -> Self { + self.max_decoding_message_size = Some(limit); + self + } + /// Limits the maximum size of an encoded message. + /// + /// Default: `usize::MAX` + #[must_use] + pub fn max_encoding_message_size(mut self, limit: usize) -> Self { + self.max_encoding_message_size = Some(limit); + self + } + } + impl tonic::codegen::Service> for PackServiceServer + where + T: PackService, + B: Body + std::marker::Send + 'static, + B::Error: Into + std::marker::Send + 'static, + { + type Response = http::Response; + type Error = std::convert::Infallible; + type Future = BoxFuture; + fn poll_ready( + &mut self, + _cx: &mut Context<'_>, + ) -> Poll> { + Poll::Ready(Ok(())) + } + fn call(&mut self, req: http::Request) -> Self::Future { + match req.uri().path() { + "/gitks.PackService/AdvertiseRefs" => { + #[allow(non_camel_case_types)] + struct AdvertiseRefsSvc(pub Arc); + impl< + T: PackService, + > tonic::server::UnaryService + for AdvertiseRefsSvc { + type Response = super::AdvertiseRefsResponse; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::advertise_refs(&inner, request).await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let method = AdvertiseRefsSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/gitks.PackService/UploadPack" => { + #[allow(non_camel_case_types)] + struct UploadPackSvc(pub Arc); + impl< + T: PackService, + > tonic::server::StreamingService + for UploadPackSvc { + type Response = super::UploadPackResponse; + type ResponseStream = T::UploadPackStream; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request< + tonic::Streaming, + >, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::upload_pack(&inner, request).await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let method = UploadPackSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.streaming(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/gitks.PackService/ReceivePack" => { + #[allow(non_camel_case_types)] + struct ReceivePackSvc(pub Arc); + impl< + T: PackService, + > tonic::server::StreamingService + for ReceivePackSvc { + type Response = super::ReceivePackResponse; + type ResponseStream = T::ReceivePackStream; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request< + tonic::Streaming, + >, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::receive_pack(&inner, request).await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let method = ReceivePackSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.streaming(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/gitks.PackService/PackObjects" => { + #[allow(non_camel_case_types)] + struct PackObjectsSvc(pub Arc); + impl< + T: PackService, + > tonic::server::ServerStreamingService + for PackObjectsSvc { + type Response = super::PackfileChunk; + type ResponseStream = T::PackObjectsStream; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::pack_objects(&inner, request).await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let method = PackObjectsSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.server_streaming(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/gitks.PackService/IndexPack" => { + #[allow(non_camel_case_types)] + struct IndexPackSvc(pub Arc); + impl< + T: PackService, + > tonic::server::ClientStreamingService + for IndexPackSvc { + type Response = super::IndexPackResponse; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request< + tonic::Streaming, + >, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::index_pack(&inner, request).await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let method = IndexPackSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.client_streaming(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/gitks.PackService/ListPackfiles" => { + #[allow(non_camel_case_types)] + struct ListPackfilesSvc(pub Arc); + impl< + T: PackService, + > tonic::server::UnaryService + for ListPackfilesSvc { + type Response = super::ListPackfilesResponse; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::list_packfiles(&inner, request).await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let method = ListPackfilesSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/gitks.PackService/Fsck" => { + #[allow(non_camel_case_types)] + struct FsckSvc(pub Arc); + impl tonic::server::UnaryService + for FsckSvc { + type Response = super::FsckResponse; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::fsck(&inner, request).await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let method = FsckSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + _ => { + Box::pin(async move { + let mut response = http::Response::new(empty_body()); + let headers = response.headers_mut(); + headers + .insert( + tonic::Status::GRPC_STATUS, + (tonic::Code::Unimplemented as i32).into(), + ); + headers + .insert( + http::header::CONTENT_TYPE, + tonic::metadata::GRPC_CONTENT_TYPE, + ); + Ok(response) + }) + } + } + } + } + impl Clone for PackServiceServer { + fn clone(&self) -> Self { + let inner = self.inner.clone(); + Self { + inner, + accept_compression_encodings: self.accept_compression_encodings, + send_compression_encodings: self.send_compression_encodings, + max_decoding_message_size: self.max_decoding_message_size, + max_encoding_message_size: self.max_encoding_message_size, + } + } + } + /// Generated gRPC service name + pub const SERVICE_NAME: &str = "gitks.PackService"; + impl tonic::server::NamedService for PackServiceServer { + const NAME: &'static str = SERVICE_NAME; + } +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct Tag { + #[prost(string, tag = "1")] + pub name: ::prost::alloc::string::String, + #[prost(string, tag = "2")] + pub full_ref: ::prost::alloc::string::String, + #[prost(message, optional, tag = "3")] + pub target_oid: ::core::option::Option, + #[prost(enumeration = "ObjectType", tag = "4")] + pub target_type: i32, + #[prost(message, optional, tag = "5")] + pub tag_oid: ::core::option::Option, + #[prost(bool, tag = "6")] + pub annotated: bool, + #[prost(message, optional, tag = "7")] + pub tagger: ::core::option::Option, + #[prost(string, tag = "8")] + pub message: ::prost::alloc::string::String, + #[prost(message, optional, tag = "9")] + pub signature: ::core::option::Option, + #[prost(bytes = "vec", tag = "10")] + pub raw: ::prost::alloc::vec::Vec, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ListTagsRequest { + #[prost(message, optional, tag = "1")] + pub repository: ::core::option::Option, + #[prost(string, tag = "2")] + pub pattern: ::prost::alloc::string::String, + #[prost(message, optional, tag = "3")] + pub pagination: ::core::option::Option, + #[prost(enumeration = "SortDirection", tag = "4")] + pub sort_direction: i32, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ListTagsResponse { + #[prost(message, repeated, tag = "1")] + pub tags: ::prost::alloc::vec::Vec, + #[prost(message, optional, tag = "2")] + pub page_info: ::core::option::Option, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct GetTagRequest { + #[prost(message, optional, tag = "1")] + pub repository: ::core::option::Option, + #[prost(string, tag = "2")] + pub name: ::prost::alloc::string::String, + #[prost(bool, tag = "3")] + pub include_raw: bool, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct CreateTagRequest { + #[prost(message, optional, tag = "1")] + pub repository: ::core::option::Option, + #[prost(string, tag = "2")] + pub name: ::prost::alloc::string::String, + #[prost(message, optional, tag = "3")] + pub target: ::core::option::Option, + #[prost(string, tag = "4")] + pub message: ::prost::alloc::string::String, + #[prost(message, optional, tag = "5")] + pub tagger: ::core::option::Option, + #[prost(bool, tag = "6")] + pub force: bool, + #[prost(bool, tag = "7")] + pub annotated: bool, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct DeleteTagRequest { + #[prost(message, optional, tag = "1")] + pub repository: ::core::option::Option, + #[prost(string, tag = "2")] + pub name: ::prost::alloc::string::String, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct VerifyTagRequest { + #[prost(message, optional, tag = "1")] + pub repository: ::core::option::Option, + #[prost(string, tag = "2")] + pub name: ::prost::alloc::string::String, +} +/// Generated client implementations. +pub mod tag_service_client { + #![allow( + unused_variables, + dead_code, + missing_docs, + clippy::wildcard_imports, + clippy::let_unit_value, + )] + use tonic::codegen::*; + use tonic::codegen::http::Uri; + #[derive(Debug, Clone)] + pub struct TagServiceClient { + inner: tonic::client::Grpc, + } + impl TagServiceClient { + /// Attempt to create a new client by connecting to a given endpoint. + pub async fn connect(dst: D) -> Result + where + D: TryInto, + D::Error: Into, + { + let conn = tonic::transport::Endpoint::new(dst)?.connect().await?; + Ok(Self::new(conn)) + } + } + impl TagServiceClient + where + T: tonic::client::GrpcService, + T::Error: Into, + T::ResponseBody: Body + std::marker::Send + 'static, + ::Error: Into + std::marker::Send, + { + pub fn new(inner: T) -> Self { + let inner = tonic::client::Grpc::new(inner); + Self { inner } + } + pub fn with_origin(inner: T, origin: Uri) -> Self { + let inner = tonic::client::Grpc::with_origin(inner, origin); + Self { inner } + } + pub fn with_interceptor( + inner: T, + interceptor: F, + ) -> TagServiceClient> + where + F: tonic::service::Interceptor, + T::ResponseBody: Default, + T: tonic::codegen::Service< + http::Request, + Response = http::Response< + >::ResponseBody, + >, + >, + , + >>::Error: Into + std::marker::Send + std::marker::Sync, + { + TagServiceClient::new(InterceptedService::new(inner, interceptor)) + } + /// Compress requests with the given encoding. + /// + /// This requires the server to support it otherwise it might respond with an + /// error. + #[must_use] + pub fn send_compressed(mut self, encoding: CompressionEncoding) -> Self { + self.inner = self.inner.send_compressed(encoding); + self + } + /// Enable decompressing responses. + #[must_use] + pub fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self { + self.inner = self.inner.accept_compressed(encoding); + self + } + /// Limits the maximum size of a decoded message. + /// + /// Default: `4MB` + #[must_use] + pub fn max_decoding_message_size(mut self, limit: usize) -> Self { + self.inner = self.inner.max_decoding_message_size(limit); + self + } + /// Limits the maximum size of an encoded message. + /// + /// Default: `usize::MAX` + #[must_use] + pub fn max_encoding_message_size(mut self, limit: usize) -> Self { + self.inner = self.inner.max_encoding_message_size(limit); + self + } + pub async fn list_tags( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + > { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic::codec::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/gitks.TagService/ListTags", + ); + let mut req = request.into_request(); + req.extensions_mut().insert(GrpcMethod::new("gitks.TagService", "ListTags")); + self.inner.unary(req, path, codec).await + } + pub async fn get_tag( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result, tonic::Status> { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic::codec::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static("/gitks.TagService/GetTag"); + let mut req = request.into_request(); + req.extensions_mut().insert(GrpcMethod::new("gitks.TagService", "GetTag")); + self.inner.unary(req, path, codec).await + } + pub async fn create_tag( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result, tonic::Status> { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic::codec::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/gitks.TagService/CreateTag", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert(GrpcMethod::new("gitks.TagService", "CreateTag")); + self.inner.unary(req, path, codec).await + } + pub async fn delete_tag( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result, tonic::Status> { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic::codec::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/gitks.TagService/DeleteTag", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert(GrpcMethod::new("gitks.TagService", "DeleteTag")); + self.inner.unary(req, path, codec).await + } + pub async fn verify_tag( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + > { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic::codec::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/gitks.TagService/VerifyTag", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert(GrpcMethod::new("gitks.TagService", "VerifyTag")); + self.inner.unary(req, path, codec).await + } + } +} +/// Generated server implementations. +pub mod tag_service_server { + #![allow( + unused_variables, + dead_code, + missing_docs, + clippy::wildcard_imports, + clippy::let_unit_value, + )] + use tonic::codegen::*; + /// Generated trait containing gRPC methods that should be implemented for use with TagServiceServer. + #[async_trait] + pub trait TagService: std::marker::Send + std::marker::Sync + 'static { + async fn list_tags( + &self, + request: tonic::Request, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + >; + async fn get_tag( + &self, + request: tonic::Request, + ) -> std::result::Result, tonic::Status>; + async fn create_tag( + &self, + request: tonic::Request, + ) -> std::result::Result, tonic::Status>; + async fn delete_tag( + &self, + request: tonic::Request, + ) -> std::result::Result, tonic::Status>; + async fn verify_tag( + &self, + request: tonic::Request, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + >; + } + #[derive(Debug)] + pub struct TagServiceServer { + inner: Arc, + accept_compression_encodings: EnabledCompressionEncodings, + send_compression_encodings: EnabledCompressionEncodings, + max_decoding_message_size: Option, + max_encoding_message_size: Option, + } + impl TagServiceServer { + pub fn new(inner: T) -> Self { + Self::from_arc(Arc::new(inner)) + } + pub fn from_arc(inner: Arc) -> Self { + Self { + inner, + accept_compression_encodings: Default::default(), + send_compression_encodings: Default::default(), + max_decoding_message_size: None, + max_encoding_message_size: None, + } + } + pub fn with_interceptor( + inner: T, + interceptor: F, + ) -> InterceptedService + where + F: tonic::service::Interceptor, + { + InterceptedService::new(Self::new(inner), interceptor) + } + /// Enable decompressing requests with the given encoding. + #[must_use] + pub fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self { + self.accept_compression_encodings.enable(encoding); + self + } + /// Compress responses with the given encoding, if the client supports it. + #[must_use] + pub fn send_compressed(mut self, encoding: CompressionEncoding) -> Self { + self.send_compression_encodings.enable(encoding); + self + } + /// Limits the maximum size of a decoded message. + /// + /// Default: `4MB` + #[must_use] + pub fn max_decoding_message_size(mut self, limit: usize) -> Self { + self.max_decoding_message_size = Some(limit); + self + } + /// Limits the maximum size of an encoded message. + /// + /// Default: `usize::MAX` + #[must_use] + pub fn max_encoding_message_size(mut self, limit: usize) -> Self { + self.max_encoding_message_size = Some(limit); + self + } + } + impl tonic::codegen::Service> for TagServiceServer + where + T: TagService, + B: Body + std::marker::Send + 'static, + B::Error: Into + std::marker::Send + 'static, + { + type Response = http::Response; + type Error = std::convert::Infallible; + type Future = BoxFuture; + fn poll_ready( + &mut self, + _cx: &mut Context<'_>, + ) -> Poll> { + Poll::Ready(Ok(())) + } + fn call(&mut self, req: http::Request) -> Self::Future { + match req.uri().path() { + "/gitks.TagService/ListTags" => { + #[allow(non_camel_case_types)] + struct ListTagsSvc(pub Arc); + impl< + T: TagService, + > tonic::server::UnaryService + for ListTagsSvc { + type Response = super::ListTagsResponse; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::list_tags(&inner, request).await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let method = ListTagsSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/gitks.TagService/GetTag" => { + #[allow(non_camel_case_types)] + struct GetTagSvc(pub Arc); + impl tonic::server::UnaryService + for GetTagSvc { + type Response = super::Tag; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::get_tag(&inner, request).await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let method = GetTagSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/gitks.TagService/CreateTag" => { + #[allow(non_camel_case_types)] + struct CreateTagSvc(pub Arc); + impl< + T: TagService, + > tonic::server::UnaryService + for CreateTagSvc { + type Response = super::Tag; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::create_tag(&inner, request).await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let method = CreateTagSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/gitks.TagService/DeleteTag" => { + #[allow(non_camel_case_types)] + struct DeleteTagSvc(pub Arc); + impl< + T: TagService, + > tonic::server::UnaryService + for DeleteTagSvc { + type Response = (); + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::delete_tag(&inner, request).await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let method = DeleteTagSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/gitks.TagService/VerifyTag" => { + #[allow(non_camel_case_types)] + struct VerifyTagSvc(pub Arc); + impl< + T: TagService, + > tonic::server::UnaryService + for VerifyTagSvc { + type Response = super::VerifiedSignature; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::verify_tag(&inner, request).await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let method = VerifyTagSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + _ => { + Box::pin(async move { + let mut response = http::Response::new(empty_body()); + let headers = response.headers_mut(); + headers + .insert( + tonic::Status::GRPC_STATUS, + (tonic::Code::Unimplemented as i32).into(), + ); + headers + .insert( + http::header::CONTENT_TYPE, + tonic::metadata::GRPC_CONTENT_TYPE, + ); + Ok(response) + }) + } + } + } + } + impl Clone for TagServiceServer { + fn clone(&self) -> Self { + let inner = self.inner.clone(); + Self { + inner, + accept_compression_encodings: self.accept_compression_encodings, + send_compression_encodings: self.send_compression_encodings, + max_decoding_message_size: self.max_decoding_message_size, + max_encoding_message_size: self.max_encoding_message_size, + } + } + } + /// Generated gRPC service name + pub const SERVICE_NAME: &str = "gitks.TagService"; + impl tonic::server::NamedService for TagServiceServer { + const NAME: &'static str = SERVICE_NAME; + } +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct TreeEntry { + #[prost(string, tag = "1")] + pub name: ::prost::alloc::string::String, + #[prost(string, tag = "2")] + pub path: ::prost::alloc::string::String, + #[prost(message, optional, tag = "3")] + pub oid: ::core::option::Option, + #[prost(enumeration = "tree_entry::EntryType", tag = "4")] + pub r#type: i32, + #[prost(uint32, tag = "5")] + pub mode: u32, + #[prost(int64, tag = "6")] + pub size: i64, +} +/// Nested message and enum types in `TreeEntry`. +pub mod tree_entry { + #[derive( + Clone, + Copy, + Debug, + PartialEq, + Eq, + Hash, + PartialOrd, + Ord, + ::prost::Enumeration + )] + #[repr(i32)] + pub enum EntryType { + TreeEntryTypeUnspecified = 0, + TreeEntryTypeTree = 1, + TreeEntryTypeBlob = 2, + TreeEntryTypeCommit = 3, + TreeEntryTypeSymlink = 4, + TreeEntryTypeExecutable = 5, + } + impl EntryType { + /// String value of the enum field names used in the ProtoBuf definition. + /// + /// The values are not transformed in any way and thus are considered stable + /// (if the ProtoBuf definition does not change) and safe for programmatic use. + pub fn as_str_name(&self) -> &'static str { + match self { + Self::TreeEntryTypeUnspecified => "TREE_ENTRY_TYPE_UNSPECIFIED", + Self::TreeEntryTypeTree => "TREE_ENTRY_TYPE_TREE", + Self::TreeEntryTypeBlob => "TREE_ENTRY_TYPE_BLOB", + Self::TreeEntryTypeCommit => "TREE_ENTRY_TYPE_COMMIT", + Self::TreeEntryTypeSymlink => "TREE_ENTRY_TYPE_SYMLINK", + Self::TreeEntryTypeExecutable => "TREE_ENTRY_TYPE_EXECUTABLE", + } + } + /// Creates an enum from field names used in the ProtoBuf definition. + pub fn from_str_name(value: &str) -> ::core::option::Option { + match value { + "TREE_ENTRY_TYPE_UNSPECIFIED" => Some(Self::TreeEntryTypeUnspecified), + "TREE_ENTRY_TYPE_TREE" => Some(Self::TreeEntryTypeTree), + "TREE_ENTRY_TYPE_BLOB" => Some(Self::TreeEntryTypeBlob), + "TREE_ENTRY_TYPE_COMMIT" => Some(Self::TreeEntryTypeCommit), + "TREE_ENTRY_TYPE_SYMLINK" => Some(Self::TreeEntryTypeSymlink), + "TREE_ENTRY_TYPE_EXECUTABLE" => Some(Self::TreeEntryTypeExecutable), + _ => None, + } + } + } +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct Tree { + #[prost(message, optional, tag = "1")] + pub oid: ::core::option::Option, + #[prost(string, tag = "2")] + pub path: ::prost::alloc::string::String, + #[prost(message, repeated, tag = "3")] + pub entries: ::prost::alloc::vec::Vec, + #[prost(bool, tag = "4")] + pub truncated: bool, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct Blob { + #[prost(message, optional, tag = "1")] + pub oid: ::core::option::Option, + #[prost(string, tag = "2")] + pub path: ::prost::alloc::string::String, + #[prost(uint32, tag = "3")] + pub mode: u32, + #[prost(int64, tag = "4")] + pub size: i64, + #[prost(bytes = "vec", tag = "5")] + pub data: ::prost::alloc::vec::Vec, + #[prost(string, tag = "6")] + pub encoding: ::prost::alloc::string::String, + #[prost(bool, tag = "7")] + pub binary: bool, + #[prost(bool, tag = "8")] + pub truncated: bool, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct FileMetadata { + #[prost(string, tag = "1")] + pub path: ::prost::alloc::string::String, + #[prost(message, optional, tag = "2")] + pub oid: ::core::option::Option, + #[prost(uint32, tag = "3")] + pub mode: u32, + #[prost(int64, tag = "4")] + pub size: i64, + #[prost(enumeration = "ObjectType", tag = "5")] + pub r#type: i32, + #[prost(bool, tag = "6")] + pub binary: bool, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ListTreeRequest { + #[prost(message, optional, tag = "1")] + pub repository: ::core::option::Option, + #[prost(message, optional, tag = "2")] + pub revision: ::core::option::Option, + #[prost(string, tag = "3")] + pub path: ::prost::alloc::string::String, + #[prost(bool, tag = "4")] + pub recursive: bool, + #[prost(message, optional, tag = "5")] + pub pagination: ::core::option::Option, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ListTreeResponse { + #[prost(message, repeated, tag = "1")] + pub entries: ::prost::alloc::vec::Vec, + #[prost(message, optional, tag = "2")] + pub page_info: ::core::option::Option, + #[prost(bool, tag = "3")] + pub truncated: bool, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct GetTreeRequest { + #[prost(message, optional, tag = "1")] + pub repository: ::core::option::Option, + #[prost(message, optional, tag = "2")] + pub revision: ::core::option::Option, + #[prost(string, tag = "3")] + pub path: ::prost::alloc::string::String, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct GetBlobRequest { + #[prost(message, optional, tag = "1")] + pub repository: ::core::option::Option, + #[prost(message, optional, tag = "2")] + pub revision: ::core::option::Option, + #[prost(string, tag = "3")] + pub path: ::prost::alloc::string::String, + #[prost(message, optional, tag = "4")] + pub oid: ::core::option::Option, + #[prost(uint64, tag = "5")] + pub max_bytes: u64, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct GetRawBlobRequest { + #[prost(message, optional, tag = "1")] + pub repository: ::core::option::Option, + #[prost(message, optional, tag = "2")] + pub revision: ::core::option::Option, + #[prost(string, tag = "3")] + pub path: ::prost::alloc::string::String, + #[prost(message, optional, tag = "4")] + pub oid: ::core::option::Option, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct GetRawBlobResponse { + #[prost(bytes = "vec", tag = "1")] + pub data: ::prost::alloc::vec::Vec, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct GetFileMetadataRequest { + #[prost(message, optional, tag = "1")] + pub repository: ::core::option::Option, + #[prost(message, optional, tag = "2")] + pub revision: ::core::option::Option, + #[prost(string, tag = "3")] + pub path: ::prost::alloc::string::String, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct FindFilesRequest { + #[prost(message, optional, tag = "1")] + pub repository: ::core::option::Option, + #[prost(message, optional, tag = "2")] + pub revision: ::core::option::Option, + #[prost(string, tag = "3")] + pub pattern: ::prost::alloc::string::String, + #[prost(string, repeated, tag = "4")] + pub pathspec: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, + #[prost(message, optional, tag = "5")] + pub pagination: ::core::option::Option, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct FindFilesResponse { + #[prost(message, repeated, tag = "1")] + pub files: ::prost::alloc::vec::Vec, + #[prost(message, optional, tag = "2")] + pub page_info: ::core::option::Option, +} +/// Generated client implementations. +pub mod tree_service_client { + #![allow( + unused_variables, + dead_code, + missing_docs, + clippy::wildcard_imports, + clippy::let_unit_value, + )] + use tonic::codegen::*; + use tonic::codegen::http::Uri; + #[derive(Debug, Clone)] + pub struct TreeServiceClient { + inner: tonic::client::Grpc, + } + impl TreeServiceClient { + /// Attempt to create a new client by connecting to a given endpoint. + pub async fn connect(dst: D) -> Result + where + D: TryInto, + D::Error: Into, + { + let conn = tonic::transport::Endpoint::new(dst)?.connect().await?; + Ok(Self::new(conn)) + } + } + impl TreeServiceClient + where + T: tonic::client::GrpcService, + T::Error: Into, + T::ResponseBody: Body + std::marker::Send + 'static, + ::Error: Into + std::marker::Send, + { + pub fn new(inner: T) -> Self { + let inner = tonic::client::Grpc::new(inner); + Self { inner } + } + pub fn with_origin(inner: T, origin: Uri) -> Self { + let inner = tonic::client::Grpc::with_origin(inner, origin); + Self { inner } + } + pub fn with_interceptor( + inner: T, + interceptor: F, + ) -> TreeServiceClient> + where + F: tonic::service::Interceptor, + T::ResponseBody: Default, + T: tonic::codegen::Service< + http::Request, + Response = http::Response< + >::ResponseBody, + >, + >, + , + >>::Error: Into + std::marker::Send + std::marker::Sync, + { + TreeServiceClient::new(InterceptedService::new(inner, interceptor)) + } + /// Compress requests with the given encoding. + /// + /// This requires the server to support it otherwise it might respond with an + /// error. + #[must_use] + pub fn send_compressed(mut self, encoding: CompressionEncoding) -> Self { + self.inner = self.inner.send_compressed(encoding); + self + } + /// Enable decompressing responses. + #[must_use] + pub fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self { + self.inner = self.inner.accept_compressed(encoding); + self + } + /// Limits the maximum size of a decoded message. + /// + /// Default: `4MB` + #[must_use] + pub fn max_decoding_message_size(mut self, limit: usize) -> Self { + self.inner = self.inner.max_decoding_message_size(limit); + self + } + /// Limits the maximum size of an encoded message. + /// + /// Default: `usize::MAX` + #[must_use] + pub fn max_encoding_message_size(mut self, limit: usize) -> Self { + self.inner = self.inner.max_encoding_message_size(limit); + self + } + pub async fn list_tree( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + > { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic::codec::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/gitks.TreeService/ListTree", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert(GrpcMethod::new("gitks.TreeService", "ListTree")); + self.inner.unary(req, path, codec).await + } + pub async fn get_tree( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result, tonic::Status> { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic::codec::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/gitks.TreeService/GetTree", + ); + let mut req = request.into_request(); + req.extensions_mut().insert(GrpcMethod::new("gitks.TreeService", "GetTree")); + self.inner.unary(req, path, codec).await + } + pub async fn get_blob( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result, tonic::Status> { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic::codec::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/gitks.TreeService/GetBlob", + ); + let mut req = request.into_request(); + req.extensions_mut().insert(GrpcMethod::new("gitks.TreeService", "GetBlob")); + self.inner.unary(req, path, codec).await + } + pub async fn get_raw_blob( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result< + tonic::Response>, + tonic::Status, + > { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic::codec::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/gitks.TreeService/GetRawBlob", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert(GrpcMethod::new("gitks.TreeService", "GetRawBlob")); + self.inner.server_streaming(req, path, codec).await + } + pub async fn get_file_metadata( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result, tonic::Status> { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic::codec::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/gitks.TreeService/GetFileMetadata", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert(GrpcMethod::new("gitks.TreeService", "GetFileMetadata")); + self.inner.unary(req, path, codec).await + } + pub async fn find_files( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + > { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic::codec::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/gitks.TreeService/FindFiles", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert(GrpcMethod::new("gitks.TreeService", "FindFiles")); + self.inner.unary(req, path, codec).await + } + } +} +/// Generated server implementations. +pub mod tree_service_server { + #![allow( + unused_variables, + dead_code, + missing_docs, + clippy::wildcard_imports, + clippy::let_unit_value, + )] + use tonic::codegen::*; + /// Generated trait containing gRPC methods that should be implemented for use with TreeServiceServer. + #[async_trait] + pub trait TreeService: std::marker::Send + std::marker::Sync + 'static { + async fn list_tree( + &self, + request: tonic::Request, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + >; + async fn get_tree( + &self, + request: tonic::Request, + ) -> std::result::Result, tonic::Status>; + async fn get_blob( + &self, + request: tonic::Request, + ) -> std::result::Result, tonic::Status>; + /// Server streaming response type for the GetRawBlob method. + type GetRawBlobStream: tonic::codegen::tokio_stream::Stream< + Item = std::result::Result, + > + + std::marker::Send + + 'static; + async fn get_raw_blob( + &self, + request: tonic::Request, + ) -> std::result::Result, tonic::Status>; + async fn get_file_metadata( + &self, + request: tonic::Request, + ) -> std::result::Result, tonic::Status>; + async fn find_files( + &self, + request: tonic::Request, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + >; + } + #[derive(Debug)] + pub struct TreeServiceServer { + inner: Arc, + accept_compression_encodings: EnabledCompressionEncodings, + send_compression_encodings: EnabledCompressionEncodings, + max_decoding_message_size: Option, + max_encoding_message_size: Option, + } + impl TreeServiceServer { + pub fn new(inner: T) -> Self { + Self::from_arc(Arc::new(inner)) + } + pub fn from_arc(inner: Arc) -> Self { + Self { + inner, + accept_compression_encodings: Default::default(), + send_compression_encodings: Default::default(), + max_decoding_message_size: None, + max_encoding_message_size: None, + } + } + pub fn with_interceptor( + inner: T, + interceptor: F, + ) -> InterceptedService + where + F: tonic::service::Interceptor, + { + InterceptedService::new(Self::new(inner), interceptor) + } + /// Enable decompressing requests with the given encoding. + #[must_use] + pub fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self { + self.accept_compression_encodings.enable(encoding); + self + } + /// Compress responses with the given encoding, if the client supports it. + #[must_use] + pub fn send_compressed(mut self, encoding: CompressionEncoding) -> Self { + self.send_compression_encodings.enable(encoding); + self + } + /// Limits the maximum size of a decoded message. + /// + /// Default: `4MB` + #[must_use] + pub fn max_decoding_message_size(mut self, limit: usize) -> Self { + self.max_decoding_message_size = Some(limit); + self + } + /// Limits the maximum size of an encoded message. + /// + /// Default: `usize::MAX` + #[must_use] + pub fn max_encoding_message_size(mut self, limit: usize) -> Self { + self.max_encoding_message_size = Some(limit); + self + } + } + impl tonic::codegen::Service> for TreeServiceServer + where + T: TreeService, + B: Body + std::marker::Send + 'static, + B::Error: Into + std::marker::Send + 'static, + { + type Response = http::Response; + type Error = std::convert::Infallible; + type Future = BoxFuture; + fn poll_ready( + &mut self, + _cx: &mut Context<'_>, + ) -> Poll> { + Poll::Ready(Ok(())) + } + fn call(&mut self, req: http::Request) -> Self::Future { + match req.uri().path() { + "/gitks.TreeService/ListTree" => { + #[allow(non_camel_case_types)] + struct ListTreeSvc(pub Arc); + impl< + T: TreeService, + > tonic::server::UnaryService + for ListTreeSvc { + type Response = super::ListTreeResponse; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::list_tree(&inner, request).await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let method = ListTreeSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/gitks.TreeService/GetTree" => { + #[allow(non_camel_case_types)] + struct GetTreeSvc(pub Arc); + impl< + T: TreeService, + > tonic::server::UnaryService + for GetTreeSvc { + type Response = super::Tree; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::get_tree(&inner, request).await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let method = GetTreeSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/gitks.TreeService/GetBlob" => { + #[allow(non_camel_case_types)] + struct GetBlobSvc(pub Arc); + impl< + T: TreeService, + > tonic::server::UnaryService + for GetBlobSvc { + type Response = super::Blob; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::get_blob(&inner, request).await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let method = GetBlobSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/gitks.TreeService/GetRawBlob" => { + #[allow(non_camel_case_types)] + struct GetRawBlobSvc(pub Arc); + impl< + T: TreeService, + > tonic::server::ServerStreamingService + for GetRawBlobSvc { + type Response = super::GetRawBlobResponse; + type ResponseStream = T::GetRawBlobStream; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::get_raw_blob(&inner, request).await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let method = GetRawBlobSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.server_streaming(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/gitks.TreeService/GetFileMetadata" => { + #[allow(non_camel_case_types)] + struct GetFileMetadataSvc(pub Arc); + impl< + T: TreeService, + > tonic::server::UnaryService + for GetFileMetadataSvc { + type Response = super::FileMetadata; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::get_file_metadata(&inner, request).await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let method = GetFileMetadataSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/gitks.TreeService/FindFiles" => { + #[allow(non_camel_case_types)] + struct FindFilesSvc(pub Arc); + impl< + T: TreeService, + > tonic::server::UnaryService + for FindFilesSvc { + type Response = super::FindFilesResponse; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::find_files(&inner, request).await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let method = FindFilesSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + _ => { + Box::pin(async move { + let mut response = http::Response::new(empty_body()); + let headers = response.headers_mut(); + headers + .insert( + tonic::Status::GRPC_STATUS, + (tonic::Code::Unimplemented as i32).into(), + ); + headers + .insert( + http::header::CONTENT_TYPE, + tonic::metadata::GRPC_CONTENT_TYPE, + ); + Ok(response) + }) + } + } + } + } + impl Clone for TreeServiceServer { + fn clone(&self) -> Self { + let inner = self.inner.clone(); + Self { + inner, + accept_compression_encodings: self.accept_compression_encodings, + send_compression_encodings: self.send_compression_encodings, + max_decoding_message_size: self.max_decoding_message_size, + max_encoding_message_size: self.max_encoding_message_size, + } + } + } + /// Generated gRPC service name + pub const SERVICE_NAME: &str = "gitks.TreeService"; + impl tonic::server::NamedService for TreeServiceServer { + const NAME: &'static str = SERVICE_NAME; + } +} diff --git a/pb/mod.rs b/pb/mod.rs new file mode 100644 index 0000000..3b64db6 --- /dev/null +++ b/pb/mod.rs @@ -0,0 +1,5 @@ +#![allow(clippy::all)] +#![allow(missing_docs)] +#![allow(rustdoc::bare_urls)] + +include!(concat!(env!("OUT_DIR"), "/gitks.rs")); diff --git a/proto/archive.proto b/proto/archive.proto new file mode 100644 index 0000000..f853037 --- /dev/null +++ b/proto/archive.proto @@ -0,0 +1,58 @@ +syntax = "proto3"; + +package gitks; + +import "oid.proto"; +import "repository.proto"; + +message ArchiveOptions { + enum Format { + ARCHIVE_FORMAT_UNSPECIFIED = 0; + ARCHIVE_FORMAT_TAR = 1; + ARCHIVE_FORMAT_TAR_GZ = 2; + ARCHIVE_FORMAT_TAR_BZ2 = 3; + ARCHIVE_FORMAT_TAR_XZ = 4; + ARCHIVE_FORMAT_ZIP = 5; + } + + Format format = 1; + string prefix = 2; + repeated string pathspec = 3; + int32 compression_level = 4; + bool include_global_extended_pax_headers = 5; +} + +message ArchiveRequest { + RepositoryHeader repository = 1; + ObjectSelector treeish = 2; + ArchiveOptions options = 3; +} + +message ArchiveChunk { + bytes data = 1; +} + +message ArchiveEntry { + string path = 1; + Oid oid = 2; + uint32 mode = 3; + int64 size = 4; + ObjectType type = 5; +} + +message ListArchiveEntriesRequest { + RepositoryHeader repository = 1; + ObjectSelector treeish = 2; + repeated string pathspec = 3; + Pagination pagination = 4; +} + +message ListArchiveEntriesResponse { + repeated ArchiveEntry entries = 1; + PageInfo page_info = 2; +} + +service ArchiveService { + rpc GetArchive(ArchiveRequest) returns (stream ArchiveChunk); + rpc ListArchiveEntries(ListArchiveEntriesRequest) returns (ListArchiveEntriesResponse); +} diff --git a/proto/blame.proto b/proto/blame.proto new file mode 100644 index 0000000..0336cb2 --- /dev/null +++ b/proto/blame.proto @@ -0,0 +1,56 @@ +syntax = "proto3"; + +package gitks; + +import "commit.proto"; +import "oid.proto"; +import "repository.proto"; + +message LineRange { + uint32 start = 1; + uint32 end = 2; +} + +message BlameOptions { + bool detect_move = 1; + bool detect_copy = 2; + uint32 score = 3; + repeated string ignore_revisions = 4; +} + +message BlameRequest { + RepositoryHeader repository = 1; + ObjectSelector revision = 2; + string path = 3; + LineRange range = 4; + BlameOptions options = 5; + Pagination pagination = 6; +} + +message BlameLine { + uint32 final_line = 1; + uint32 original_line = 2; + bytes content = 3; +} + +message BlameHunk { + Commit commit = 1; + string original_path = 2; + string final_path = 3; + uint32 original_start_line = 4; + uint32 final_start_line = 5; + uint32 line_count = 6; + bool boundary = 7; + repeated BlameLine lines = 8; +} + +message BlameResponse { + repeated BlameHunk hunks = 1; + PageInfo page_info = 2; + bool truncated = 3; +} + +service BlameService { + rpc Blame(BlameRequest) returns (BlameResponse); + rpc StreamBlame(BlameRequest) returns (stream BlameHunk); +} diff --git a/proto/branch.proto b/proto/branch.proto new file mode 100644 index 0000000..3411d19 --- /dev/null +++ b/proto/branch.proto @@ -0,0 +1,114 @@ +syntax = "proto3"; + +package gitks; + +import "google/protobuf/empty.proto"; +import "commit.proto"; +import "oid.proto"; +import "repository.proto"; + +message BranchUpstream { + string remote_name = 1; + string remote_url = 2; + string remote_branch_name = 3; + string local_branch_name = 4; +} + +message Branch { + string name = 1; + string full_ref = 2; + Oid target_oid = 3; + Commit commit = 4; + BranchUpstream upstream = 5; + bool is_default = 6; + bool is_head = 7; + bool is_merged = 8; + bool is_detached = 9; +} + +// Backward-compatible payload name used by earlier clients. +message PayloadBranch { + PayloadCommit commit = 1; + string name = 2; + BranchUpstream upstream = 3; + bool is_head = 4; +} + +message RequestBranchInit {} + +message ListBranchesRequest { + RepositoryHeader repository = 1; + string pattern = 2; + bool merged_into_head = 3; + bool not_merged_into_head = 4; + Pagination pagination = 5; + SortDirection sort_direction = 6; +} + +message ListBranchesResponse { + repeated Branch branches = 1; + PageInfo page_info = 2; +} + +message GetBranchRequest { + RepositoryHeader repository = 1; + string name = 2; +} + +message CreateBranchRequest { + RepositoryHeader repository = 1; + string name = 2; + ObjectSelector start_point = 3; + bool force = 4; +} + +message DeleteBranchRequest { + RepositoryHeader repository = 1; + string name = 2; + bool force = 3; +} + +message RenameBranchRequest { + RepositoryHeader repository = 1; + string old_name = 2; + string new_name = 3; +} + +message UpdateBranchTargetRequest { + RepositoryHeader repository = 1; + string name = 2; + Oid expected_old_oid = 3; + Oid new_oid = 4; + bool force = 5; +} + +message SetBranchUpstreamRequest { + RepositoryHeader repository = 1; + string name = 2; + BranchUpstream upstream = 3; +} + +message CompareBranchRequest { + RepositoryHeader repository = 1; + string source_branch = 2; + string target_branch = 3; +} + +message CompareBranchResponse { + bool ahead = 1; + bool behind = 2; + uint32 ahead_by = 3; + uint32 behind_by = 4; + Oid merge_base = 5; +} + +service BranchService { + rpc ListBranches(ListBranchesRequest) returns (ListBranchesResponse); + rpc GetBranch(GetBranchRequest) returns (Branch); + rpc CreateBranch(CreateBranchRequest) returns (Branch); + rpc DeleteBranch(DeleteBranchRequest) returns (google.protobuf.Empty); + rpc RenameBranch(RenameBranchRequest) returns (Branch); + rpc UpdateBranchTarget(UpdateBranchTargetRequest) returns (Branch); + rpc SetBranchUpstream(SetBranchUpstreamRequest) returns (Branch); + rpc CompareBranch(CompareBranchRequest) returns (CompareBranchResponse); +} diff --git a/proto/commit.proto b/proto/commit.proto new file mode 100644 index 0000000..ec293fe --- /dev/null +++ b/proto/commit.proto @@ -0,0 +1,165 @@ +syntax = "proto3"; + +package gitks; + +import "google/protobuf/timestamp.proto"; +import "oid.proto"; +import "repository.proto"; +import "tagger.proto"; + +message PayloadCommit { + PayloadTagger author = 1; + PayloadTagger committer = 2; + Oid oid = 3; + string message = 4; + repeated Oid parents = 5; + Oid tree = 6; + google.protobuf.Timestamp timestamp = 7; +} + +message CommitTrailer { + string key = 1; + string value = 2; + bool separator_present = 3; +} + +message CommitStats { + uint32 additions = 1; + uint32 deletions = 2; + uint32 changed_files = 3; +} + +message Commit { + Oid oid = 1; + string abbreviated_oid = 2; + repeated Oid parent_oids = 3; + Oid tree_oid = 4; + Signature author = 5; + Signature committer = 6; + string subject = 7; + string body = 8; + string message = 9; + repeated CommitTrailer trailers = 10; + VerifiedSignature signature = 11; + CommitStats stats = 12; + google.protobuf.Timestamp authored_at = 13; + google.protobuf.Timestamp committed_at = 14; + bytes raw = 15; +} + +message ListCommitsRequest { + RepositoryHeader repository = 1; + ObjectSelector revision = 2; + string path = 3; + google.protobuf.Timestamp since = 4; + google.protobuf.Timestamp until = 5; + bool first_parent = 6; + bool all = 7; + bool reverse = 8; + uint32 max_parents = 9; + uint32 min_parents = 10; + Pagination pagination = 11; +} + +message ListCommitsResponse { + repeated Commit commits = 1; + PageInfo page_info = 2; +} + +message GetCommitRequest { + RepositoryHeader repository = 1; + ObjectSelector revision = 2; + bool include_stats = 3; + bool include_raw = 4; +} + +message GetCommitAncestorsRequest { + RepositoryHeader repository = 1; + ObjectSelector revision = 2; + bool first_parent = 3; + Pagination pagination = 4; +} + +message GetCommitAncestorsResponse { + repeated Commit commits = 1; + PageInfo page_info = 2; +} + +message CreateCommitAction { + enum Action { + CREATE_COMMIT_ACTION_UNSPECIFIED = 0; + CREATE_COMMIT_ACTION_CREATE = 1; + CREATE_COMMIT_ACTION_UPDATE = 2; + CREATE_COMMIT_ACTION_DELETE = 3; + CREATE_COMMIT_ACTION_MOVE = 4; + CREATE_COMMIT_ACTION_CHMOD = 5; + } + + Action action = 1; + string file_path = 2; + string previous_path = 3; + bytes content = 4; + string encoding = 5; + bool executable = 6; + Oid last_commit_oid = 7; +} + +message CreateCommitRequest { + RepositoryHeader repository = 1; + string branch = 2; + string message = 3; + Signature author = 4; + Signature committer = 5; + repeated CreateCommitAction actions = 6; + ObjectSelector start_revision = 7; + bool force = 8; + repeated CommitTrailer trailers = 9; +} + +message CreateCommitResponse { + Commit commit = 1; + string branch = 2; +} + +message RevertCommitRequest { + RepositoryHeader repository = 1; + ObjectSelector commit = 2; + string branch = 3; + Signature committer = 4; + string message = 5; +} + +message CherryPickCommitRequest { + RepositoryHeader repository = 1; + ObjectSelector commit = 2; + string branch = 3; + Signature committer = 4; + string message = 5; + uint32 mainline = 6; +} + +message CompareCommitsRequest { + RepositoryHeader repository = 1; + ObjectSelector base = 2; + ObjectSelector head = 3; + bool straight = 4; + bool first_parent = 5; + Pagination pagination = 6; +} + +message CompareCommitsResponse { + repeated Commit commits = 1; + CommitStats stats = 2; + PageInfo page_info = 3; + Oid merge_base = 4; +} + +service CommitService { + rpc ListCommits(ListCommitsRequest) returns (ListCommitsResponse); + rpc GetCommit(GetCommitRequest) returns (Commit); + rpc GetCommitAncestors(GetCommitAncestorsRequest) returns (GetCommitAncestorsResponse); + rpc CreateCommit(CreateCommitRequest) returns (CreateCommitResponse); + rpc RevertCommit(RevertCommitRequest) returns (CreateCommitResponse); + rpc CherryPickCommit(CherryPickCommitRequest) returns (CreateCommitResponse); + rpc CompareCommits(CompareCommitsRequest) returns (CompareCommitsResponse); +} diff --git a/proto/diff.proto b/proto/diff.proto new file mode 100644 index 0000000..8d6b346 --- /dev/null +++ b/proto/diff.proto @@ -0,0 +1,140 @@ +syntax = "proto3"; + +package gitks; + +import "oid.proto"; +import "repository.proto"; + +message DiffOptions { + enum WhitespaceMode { + DIFF_WHITESPACE_MODE_UNSPECIFIED = 0; + DIFF_WHITESPACE_MODE_DEFAULT = 1; + DIFF_WHITESPACE_MODE_IGNORE_ALL = 2; + DIFF_WHITESPACE_MODE_IGNORE_CHANGE = 3; + DIFF_WHITESPACE_MODE_IGNORE_EOL = 4; + } + + bool recursive = 1; + bool include_binary = 2; + bool include_patch = 3; + bool rename_detection = 4; + bool copy_detection = 5; + uint32 context_lines = 6; + repeated string pathspec = 7; + WhitespaceMode whitespace_mode = 8; + uint64 max_files = 9; + uint64 max_bytes = 10; +} + +message DiffLine { + enum LineType { + DIFF_LINE_TYPE_UNSPECIFIED = 0; + DIFF_LINE_TYPE_CONTEXT = 1; + DIFF_LINE_TYPE_ADDED = 2; + DIFF_LINE_TYPE_DELETED = 3; + DIFF_LINE_TYPE_HUNK_HEADER = 4; + DIFF_LINE_TYPE_NO_NEWLINE = 5; + } + + LineType type = 1; + int32 old_line = 2; + int32 new_line = 3; + bytes content = 4; + bool truncated = 5; +} + +message DiffHunk { + string header = 1; + uint32 old_start = 2; + uint32 old_lines = 3; + uint32 new_start = 4; + uint32 new_lines = 5; + repeated DiffLine lines = 6; +} + +message DiffFile { + enum ChangeType { + DIFF_FILE_CHANGE_TYPE_UNSPECIFIED = 0; + DIFF_FILE_CHANGE_TYPE_ADDED = 1; + DIFF_FILE_CHANGE_TYPE_MODIFIED = 2; + DIFF_FILE_CHANGE_TYPE_DELETED = 3; + DIFF_FILE_CHANGE_TYPE_RENAMED = 4; + DIFF_FILE_CHANGE_TYPE_COPIED = 5; + DIFF_FILE_CHANGE_TYPE_TYPE_CHANGED = 6; + DIFF_FILE_CHANGE_TYPE_UNMERGED = 7; + } + + string old_path = 1; + string new_path = 2; + Oid old_oid = 3; + Oid new_oid = 4; + uint32 old_mode = 5; + uint32 new_mode = 6; + ChangeType change_type = 7; + bool binary = 8; + bool too_large = 9; + uint32 additions = 10; + uint32 deletions = 11; + repeated DiffHunk hunks = 12; + bytes patch = 13; + double similarity = 14; +} + +message DiffStats { + uint32 additions = 1; + uint32 deletions = 2; + uint32 changed_files = 3; +} + +message Diff { + repeated DiffFile files = 1; + DiffStats stats = 2; + bool overflow = 3; +} + +message GetDiffRequest { + RepositoryHeader repository = 1; + ObjectSelector base = 2; + ObjectSelector head = 3; + DiffOptions options = 4; + Pagination pagination = 5; +} + +message GetDiffResponse { + repeated DiffFile files = 1; + DiffStats stats = 2; + PageInfo page_info = 3; + bool overflow = 4; +} + +message GetCommitDiffRequest { + RepositoryHeader repository = 1; + ObjectSelector commit = 2; + DiffOptions options = 3; + Pagination pagination = 4; +} + +message GetPatchRequest { + RepositoryHeader repository = 1; + ObjectSelector base = 2; + ObjectSelector head = 3; + DiffOptions options = 4; +} + +message GetPatchResponse { + bytes data = 1; +} + +message GetDiffStatsRequest { + RepositoryHeader repository = 1; + ObjectSelector base = 2; + ObjectSelector head = 3; + DiffOptions options = 4; +} + +service DiffService { + rpc GetDiff(GetDiffRequest) returns (GetDiffResponse); + rpc GetCommitDiff(GetCommitDiffRequest) returns (GetDiffResponse); + rpc GetPatch(GetPatchRequest) returns (stream GetPatchResponse); + rpc GetDiffStats(GetDiffStatsRequest) returns (DiffStats); +} diff --git a/proto/merge.proto b/proto/merge.proto new file mode 100644 index 0000000..e0ff940 --- /dev/null +++ b/proto/merge.proto @@ -0,0 +1,139 @@ +syntax = "proto3"; + +package gitks; + +import "commit.proto"; +import "diff.proto"; +import "oid.proto"; +import "repository.proto"; +import "tagger.proto"; + +message MergeOptions { + enum Strategy { + MERGE_STRATEGY_UNSPECIFIED = 0; + MERGE_STRATEGY_RECURSIVE = 1; + MERGE_STRATEGY_ORT = 2; + MERGE_STRATEGY_RESOLVE = 3; + MERGE_STRATEGY_OCTOPUS = 4; + MERGE_STRATEGY_OURS = 5; + MERGE_STRATEGY_SUBTREE = 6; + } + + enum FastForwardMode { + MERGE_FAST_FORWARD_MODE_UNSPECIFIED = 0; + MERGE_FAST_FORWARD_MODE_ALLOWED = 1; + MERGE_FAST_FORWARD_MODE_ONLY = 2; + MERGE_FAST_FORWARD_MODE_NO_FF = 3; + } + + Strategy strategy = 1; + FastForwardMode fast_forward = 2; + bool squash = 3; + bool no_commit = 4; + bool allow_unrelated_histories = 5; + repeated string strategy_options = 6; +} + +message MergeConflictSection { + string label = 1; + bytes content = 2; +} + +message MergeConflict { + string path = 1; + uint32 mode = 2; + Oid base_oid = 3; + Oid ours_oid = 4; + Oid theirs_oid = 5; + repeated MergeConflictSection sections = 6; + bool binary = 7; +} + +message MergeResult { + enum Status { + MERGE_RESULT_STATUS_UNSPECIFIED = 0; + MERGE_RESULT_STATUS_MERGED = 1; + MERGE_RESULT_STATUS_FAST_FORWARD = 2; + MERGE_RESULT_STATUS_ALREADY_UP_TO_DATE = 3; + MERGE_RESULT_STATUS_CONFLICTS = 4; + MERGE_RESULT_STATUS_ABORTED = 5; + } + + Status status = 1; + Commit commit = 2; + Oid merge_base = 3; + repeated MergeConflict conflicts = 4; + DiffStats stats = 5; + string message = 6; +} + +message MergeRequest { + RepositoryHeader repository = 1; + string target_branch = 2; + ObjectSelector source = 3; + Signature committer = 4; + string message = 5; + MergeOptions options = 6; +} + +message CheckMergeRequest { + RepositoryHeader repository = 1; + ObjectSelector target = 2; + ObjectSelector source = 3; + MergeOptions options = 4; +} + +message ListMergeConflictsRequest { + RepositoryHeader repository = 1; + ObjectSelector target = 2; + ObjectSelector source = 3; + Pagination pagination = 4; +} + +message ListMergeConflictsResponse { + repeated MergeConflict conflicts = 1; + PageInfo page_info = 2; +} + +message ResolveMergeConflict { + string path = 1; + bytes content = 2; +} + +message ResolveMergeConflictsRequest { + RepositoryHeader repository = 1; + string target_branch = 2; + ObjectSelector source = 3; + repeated ResolveMergeConflict resolutions = 4; + Signature committer = 5; + string message = 6; +} + +message RebaseRequest { + RepositoryHeader repository = 1; + string branch = 2; + ObjectSelector upstream = 3; + Signature committer = 4; +} + +message RebaseResult { + enum Status { + REBASE_RESULT_STATUS_UNSPECIFIED = 0; + REBASE_RESULT_STATUS_REBASED = 1; + REBASE_RESULT_STATUS_ALREADY_UP_TO_DATE = 2; + REBASE_RESULT_STATUS_CONFLICTS = 3; + REBASE_RESULT_STATUS_ABORTED = 4; + } + + Status status = 1; + Commit head = 2; + repeated MergeConflict conflicts = 3; +} + +service MergeService { + rpc CheckMerge(CheckMergeRequest) returns (MergeResult); + rpc Merge(MergeRequest) returns (MergeResult); + rpc ListMergeConflicts(ListMergeConflictsRequest) returns (ListMergeConflictsResponse); + rpc ResolveMergeConflicts(ResolveMergeConflictsRequest) returns (MergeResult); + rpc Rebase(RebaseRequest) returns (RebaseResult); +} diff --git a/proto/oid.proto b/proto/oid.proto new file mode 100644 index 0000000..f5af413 --- /dev/null +++ b/proto/oid.proto @@ -0,0 +1,64 @@ +syntax = "proto3"; + +package gitks; + +// Git object hash algorithm. GitHub and Gitaly both need to support SHA-1 today +// and SHA-256 repositories as they become more common. +enum ObjectFormat { + OBJECT_FORMAT_UNSPECIFIED = 0; + OBJECT_FORMAT_SHA1 = 1; + OBJECT_FORMAT_SHA256 = 2; +} + +// Git object kind. +enum ObjectType { + OBJECT_TYPE_UNSPECIFIED = 0; + OBJECT_TYPE_COMMIT = 1; + OBJECT_TYPE_TREE = 2; + OBJECT_TYPE_BLOB = 3; + OBJECT_TYPE_TAG = 4; +} + +// Canonical object id. `value` preserves the original binary representation used +// by the existing API; `hex` is the normalized lowercase hex form for clients. +message Oid { + bytes value = 1; + string hex = 2; + ObjectFormat format = 3; +} + +message ObjectName { + // Revision expression, refname, oid hex, or pseudo-ref such as HEAD. + string revision = 1; +} + +message ObjectSelector { + oneof selector { + Oid oid = 1; + ObjectName revision = 2; + } +} + +message ObjectIdentity { + Oid oid = 1; + ObjectType type = 2; + int64 size = 3; + string abbreviated_oid = 4; +} + +message Pagination { + uint32 page_size = 1; + string page_token = 2; +} + +message PageInfo { + string next_page_token = 1; + bool has_next_page = 2; + uint64 total_count = 3; +} + +enum SortDirection { + SORT_DIRECTION_UNSPECIFIED = 0; + SORT_DIRECTION_ASC = 1; + SORT_DIRECTION_DESC = 2; +} diff --git a/proto/pack.proto b/proto/pack.proto new file mode 100644 index 0000000..b3ee87b --- /dev/null +++ b/proto/pack.proto @@ -0,0 +1,134 @@ +syntax = "proto3"; + +package gitks; + +import "oid.proto"; +import "repository.proto"; + +message GitProtocolFeatures { + uint32 version = 1; + repeated string capabilities = 2; + repeated string server_options = 3; + repeated string agent = 4; +} + +message ReferenceAdvertisement { + string name = 1; + Oid target_oid = 2; + Oid peeled_oid = 3; + bool symbolic = 4; + string symbolic_target = 5; +} + +message AdvertiseRefsRequest { + RepositoryHeader repository = 1; + GitProtocolFeatures protocol = 2; + string service = 3; +} + +message AdvertiseRefsResponse { + repeated ReferenceAdvertisement references = 1; + repeated string capabilities = 2; +} + +message UploadPackRequest { + RepositoryHeader repository = 1; + GitProtocolFeatures protocol = 2; + bytes packet = 3; + bool done = 4; +} + +message UploadPackResponse { + bytes packet = 1; + string stderr = 2; +} + +message ReceivePackRequest { + RepositoryHeader repository = 1; + GitProtocolFeatures protocol = 2; + bytes packet = 3; + bool done = 4; +} + +message ReceivePackResponse { + bytes packet = 1; + string stderr = 2; +} + +message PackObjectsOptions { + repeated Oid wants = 1; + repeated Oid haves = 2; + repeated string shallow_revisions = 3; + uint32 deepen = 4; + bool thin_pack = 5; + bool include_tag = 6; + bool use_bitmaps = 7; + bool delta_base_offset = 8; + repeated string pathspec = 9; +} + +message PackObjectsRequest { + RepositoryHeader repository = 1; + PackObjectsOptions options = 2; +} + +message PackfileChunk { + bytes data = 1; +} + +message IndexPackRequest { + RepositoryHeader repository = 1; + bytes data = 2; + bool done = 3; + bool keep = 4; + bool strict = 5; +} + +message IndexPackResponse { + Oid pack_hash = 1; + uint64 object_count = 2; + string stderr = 3; +} + +message ListPackfilesRequest { + RepositoryHeader repository = 1; + Pagination pagination = 2; +} + +message PackfileInfo { + string name = 1; + Oid pack_hash = 2; + uint64 size_bytes = 3; + uint64 index_size_bytes = 4; + uint64 object_count = 5; + bool has_bitmap = 6; + bool has_rev_index = 7; + bool kept = 8; +} + +message ListPackfilesResponse { + repeated PackfileInfo packfiles = 1; + PageInfo page_info = 2; +} + +message FsckRequest { + RepositoryHeader repository = 1; + bool strict = 2; + bool connectivity_only = 3; +} + +message FsckResponse { + bool ok = 1; + repeated string errors = 2; + repeated string warnings = 3; +} + +service PackService { + rpc AdvertiseRefs(AdvertiseRefsRequest) returns (AdvertiseRefsResponse); + rpc UploadPack(stream UploadPackRequest) returns (stream UploadPackResponse); + rpc ReceivePack(stream ReceivePackRequest) returns (stream ReceivePackResponse); + rpc PackObjects(PackObjectsRequest) returns (stream PackfileChunk); + rpc IndexPack(stream IndexPackRequest) returns (IndexPackResponse); + rpc ListPackfiles(ListPackfilesRequest) returns (ListPackfilesResponse); + rpc Fsck(FsckRequest) returns (FsckResponse); +} diff --git a/proto/repository.proto b/proto/repository.proto new file mode 100644 index 0000000..d9b1ebb --- /dev/null +++ b/proto/repository.proto @@ -0,0 +1,157 @@ +syntax = "proto3"; + +package gitks; + +import "google/protobuf/empty.proto"; +import "oid.proto"; + +// Repository identity used by storage-facing RPCs. +message RepositoryHeader { + // Logical storage shard or disk name. + string storage_name = 1; + // Path relative to the storage root, usually ending in `.git` for bare repos. + string relative_path = 2; + // Optional absolute path for embedded/local deployments. + string storage_path = 3; +} + +message Repository { + RepositoryHeader header = 1; + bool bare = 2; + bool empty = 3; + ObjectFormat object_format = 4; + string default_branch = 5; + string git_object_directory = 6; + repeated string git_alternate_object_directories = 7; +} + +message RepositoryStatistics { + uint64 size_bytes = 1; + uint64 loose_object_count = 2; + uint64 packed_object_count = 3; + uint64 packfile_count = 4; + uint64 reference_count = 5; + uint64 commit_graph_size_bytes = 6; + uint64 multi_pack_index_size_bytes = 7; +} + +message RepositoryConfigEntry { + string key = 1; + repeated string values = 2; +} + +message RepositoryObjectFormatRequest { + RepositoryHeader repository = 1; +} + +message RepositoryObjectFormatResponse { + ObjectFormat object_format = 1; +} + +message GetRepositoryRequest { + RepositoryHeader repository = 1; +} + +message InitRepositoryRequest { + RepositoryHeader repository = 1; + bool bare = 2; + ObjectFormat object_format = 3; + string initial_branch = 4; +} + +message DeleteRepositoryRequest { + RepositoryHeader repository = 1; +} + +message RepositoryExistsRequest { + RepositoryHeader repository = 1; +} + +message RepositoryExistsResponse { + bool exists = 1; +} + +message GetDefaultBranchRequest { + RepositoryHeader repository = 1; +} + +message GetDefaultBranchResponse { + string name = 1; +} + +message SetDefaultBranchRequest { + RepositoryHeader repository = 1; + string name = 2; +} + +message GetRepositoryConfigRequest { + RepositoryHeader repository = 1; + repeated string keys = 2; +} + +message GetRepositoryConfigResponse { + repeated RepositoryConfigEntry entries = 1; +} + +message SetRepositoryConfigRequest { + RepositoryHeader repository = 1; + repeated RepositoryConfigEntry entries = 2; +} + +message RepositoryStatisticsRequest { + RepositoryHeader repository = 1; +} + +message RepositoryHealthRequest { + RepositoryHeader repository = 1; + bool connectivity_only = 2; +} + +message RepositoryHealthResponse { + bool ok = 1; + repeated string warnings = 2; + repeated string errors = 3; + RepositoryStatistics statistics = 4; +} + +message GarbageCollectRequest { + RepositoryHeader repository = 1; + bool prune = 2; + bool aggressive = 3; +} + +message RepackRequest { + RepositoryHeader repository = 1; + bool full = 2; + bool write_bitmaps = 3; + bool write_multi_pack_index = 4; +} + +message WriteCommitGraphRequest { + RepositoryHeader repository = 1; + bool replace = 2; + bool split = 3; +} + +message RepositoryMaintenanceResponse { + bool ok = 1; + string stdout = 2; + string stderr = 3; +} + +service RepositoryService { + rpc GetRepository(GetRepositoryRequest) returns (Repository); + rpc InitRepository(InitRepositoryRequest) returns (Repository); + rpc DeleteRepository(DeleteRepositoryRequest) returns (google.protobuf.Empty); + rpc RepositoryExists(RepositoryExistsRequest) returns (RepositoryExistsResponse); + rpc GetObjectFormat(RepositoryObjectFormatRequest) returns (RepositoryObjectFormatResponse); + rpc GetDefaultBranch(GetDefaultBranchRequest) returns (GetDefaultBranchResponse); + rpc SetDefaultBranch(SetDefaultBranchRequest) returns (google.protobuf.Empty); + rpc GetRepositoryConfig(GetRepositoryConfigRequest) returns (GetRepositoryConfigResponse); + rpc SetRepositoryConfig(SetRepositoryConfigRequest) returns (google.protobuf.Empty); + rpc GetRepositoryStatistics(RepositoryStatisticsRequest) returns (RepositoryStatistics); + rpc CheckRepositoryHealth(RepositoryHealthRequest) returns (RepositoryHealthResponse); + rpc GarbageCollect(GarbageCollectRequest) returns (RepositoryMaintenanceResponse); + rpc Repack(RepackRequest) returns (RepositoryMaintenanceResponse); + rpc WriteCommitGraph(WriteCommitGraphRequest) returns (RepositoryMaintenanceResponse); +} diff --git a/proto/tag.proto b/proto/tag.proto new file mode 100644 index 0000000..fc02e2e --- /dev/null +++ b/proto/tag.proto @@ -0,0 +1,67 @@ +syntax = "proto3"; + +package gitks; + +import "google/protobuf/empty.proto"; +import "oid.proto"; +import "repository.proto"; +import "tagger.proto"; + +message Tag { + string name = 1; + string full_ref = 2; + Oid target_oid = 3; + ObjectType target_type = 4; + Oid tag_oid = 5; + bool annotated = 6; + Signature tagger = 7; + string message = 8; + VerifiedSignature signature = 9; + bytes raw = 10; +} + +message ListTagsRequest { + RepositoryHeader repository = 1; + string pattern = 2; + Pagination pagination = 3; + SortDirection sort_direction = 4; +} + +message ListTagsResponse { + repeated Tag tags = 1; + PageInfo page_info = 2; +} + +message GetTagRequest { + RepositoryHeader repository = 1; + string name = 2; + bool include_raw = 3; +} + +message CreateTagRequest { + RepositoryHeader repository = 1; + string name = 2; + ObjectSelector target = 3; + string message = 4; + Signature tagger = 5; + bool force = 6; + bool annotated = 7; +} + +message DeleteTagRequest { + RepositoryHeader repository = 1; + string name = 2; +} + +message VerifyTagRequest { + RepositoryHeader repository = 1; + string name = 2; +} + +service TagService { + rpc ListTags(ListTagsRequest) returns (ListTagsResponse); + rpc GetTag(GetTagRequest) returns (Tag); + rpc CreateTag(CreateTagRequest) returns (Tag); + rpc DeleteTag(DeleteTagRequest) returns (google.protobuf.Empty); + rpc VerifyTag(VerifyTagRequest) returns (VerifiedSignature); +} diff --git a/proto/tagger.proto b/proto/tagger.proto new file mode 100644 index 0000000..cdfb408 --- /dev/null +++ b/proto/tagger.proto @@ -0,0 +1,51 @@ +syntax = "proto3"; + +package gitks; + +import "google/protobuf/timestamp.proto"; + +// Git identity attached to commits and tags. +message Identity { + string name = 1; + string email = 2; +} + +// Git signature with timestamp and timezone offset. +message Signature { + Identity identity = 1; + google.protobuf.Timestamp when = 2; + // Offset in minutes east of UTC, as stored by git. + int32 timezone_offset = 3; +} + +// Backward-compatible payload name used by earlier Rust structs. +message PayloadTagger { + string email = 1; + string name = 2; +} + +message VerifiedSignature { + enum Reason { + REASON_UNSPECIFIED = 0; + REASON_VALID = 1; + REASON_EXPIRED_KEY = 2; + REASON_NOT_SIGNING_KEY = 3; + REASON_GPGVERIFY_ERROR = 4; + REASON_GPGVERIFY_UNAVAILABLE = 5; + REASON_UNSIGNED = 6; + REASON_UNKNOWN_SIGNATURE_TYPE = 7; + REASON_NO_USER = 8; + REASON_UNVERIFIED_EMAIL = 9; + REASON_BAD_EMAIL = 10; + REASON_UNKNOWN_KEY = 11; + REASON_MALFORMED_SIGNATURE = 12; + REASON_INVALID = 13; + } + + bool verified = 1; + Reason reason = 2; + string signature = 3; + string payload = 4; + string key_fingerprint = 5; + string signer = 6; +} diff --git a/proto/tree.proto b/proto/tree.proto new file mode 100644 index 0000000..848d1ca --- /dev/null +++ b/proto/tree.proto @@ -0,0 +1,118 @@ +syntax = "proto3"; + +package gitks; + +import "oid.proto"; +import "repository.proto"; + +message TreeEntry { + enum EntryType { + TREE_ENTRY_TYPE_UNSPECIFIED = 0; + TREE_ENTRY_TYPE_TREE = 1; + TREE_ENTRY_TYPE_BLOB = 2; + TREE_ENTRY_TYPE_COMMIT = 3; + TREE_ENTRY_TYPE_SYMLINK = 4; + TREE_ENTRY_TYPE_EXECUTABLE = 5; + } + + string name = 1; + string path = 2; + Oid oid = 3; + EntryType type = 4; + uint32 mode = 5; + int64 size = 6; +} + +message Tree { + Oid oid = 1; + string path = 2; + repeated TreeEntry entries = 3; + bool truncated = 4; +} + +message Blob { + Oid oid = 1; + string path = 2; + uint32 mode = 3; + int64 size = 4; + bytes data = 5; + string encoding = 6; + bool binary = 7; + bool truncated = 8; +} + +message FileMetadata { + string path = 1; + Oid oid = 2; + uint32 mode = 3; + int64 size = 4; + ObjectType type = 5; + bool binary = 6; +} + +message ListTreeRequest { + RepositoryHeader repository = 1; + ObjectSelector revision = 2; + string path = 3; + bool recursive = 4; + Pagination pagination = 5; +} + +message ListTreeResponse { + repeated TreeEntry entries = 1; + PageInfo page_info = 2; + bool truncated = 3; +} + +message GetTreeRequest { + RepositoryHeader repository = 1; + ObjectSelector revision = 2; + string path = 3; +} + +message GetBlobRequest { + RepositoryHeader repository = 1; + ObjectSelector revision = 2; + string path = 3; + Oid oid = 4; + uint64 max_bytes = 5; +} + +message GetRawBlobRequest { + RepositoryHeader repository = 1; + ObjectSelector revision = 2; + string path = 3; + Oid oid = 4; +} + +message GetRawBlobResponse { + bytes data = 1; +} + +message GetFileMetadataRequest { + RepositoryHeader repository = 1; + ObjectSelector revision = 2; + string path = 3; +} + +message FindFilesRequest { + RepositoryHeader repository = 1; + ObjectSelector revision = 2; + string pattern = 3; + repeated string pathspec = 4; + Pagination pagination = 5; +} + +message FindFilesResponse { + repeated FileMetadata files = 1; + PageInfo page_info = 2; +} + +service TreeService { + rpc ListTree(ListTreeRequest) returns (ListTreeResponse); + rpc GetTree(GetTreeRequest) returns (Tree); + rpc GetBlob(GetBlobRequest) returns (Blob); + rpc GetRawBlob(GetRawBlobRequest) returns (stream GetRawBlobResponse); + rpc GetFileMetadata(GetFileMetadataRequest) returns (FileMetadata); + rpc FindFiles(FindFilesRequest) returns (FindFilesResponse); +} diff --git a/refs/list_refs.rs b/refs/list_refs.rs new file mode 100644 index 0000000..e510e62 --- /dev/null +++ b/refs/list_refs.rs @@ -0,0 +1,25 @@ +use crate::bare::GitBare; +use crate::error::GitResult; +use crate::pb::ReferenceAdvertisement; + +impl GitBare { + pub fn list_refs(&self) -> GitResult> { + let repo = self.gix_repo()?; + let mut refs = Vec::new(); + for r in repo.references()?.all()? { + let mut r = match r { + Ok(r) => r, + Err(_) => continue, + }; + let hex = r.peel_to_id().map(|id| id.to_string()).unwrap_or_default(); + refs.push(ReferenceAdvertisement { + name: r.name().to_string(), + target_oid: Some(self.oid_to_pb(hex)), + peeled_oid: None, + symbolic: r.target().try_id().is_none(), + symbolic_target: String::new(), + }); + } + Ok(refs) + } +} diff --git a/refs/mod.rs b/refs/mod.rs new file mode 100644 index 0000000..a228a0f --- /dev/null +++ b/refs/mod.rs @@ -0,0 +1 @@ +pub mod list_refs; diff --git a/tag/create_tag.rs b/tag/create_tag.rs new file mode 100644 index 0000000..054ba6b --- /dev/null +++ b/tag/create_tag.rs @@ -0,0 +1,46 @@ +use crate::bare::GitBare; +use crate::error::{GitError, GitResult}; +use crate::pb::{CreateTagRequest, GetTagRequest, Tag}; + +impl GitBare { + pub fn create_tag(&self, request: CreateTagRequest) -> GitResult { + let target = match request.target.and_then(|s| s.selector) { + Some(crate::pb::object_selector::Selector::Oid(oid)) => oid.hex, + Some(crate::pb::object_selector::Selector::Revision(name)) => name.revision, + None => "HEAD".into(), + }; + let mut args = vec![ + "--git-dir".to_string(), + self.bare_dir.to_string_lossy().into_owned(), + "tag".to_string(), + ]; + if request.force { + args.push("--force".into()); + } + if request.annotated { + args.push("--annotate".into()); + if !request.message.is_empty() { + args.push("-m".into()); + args.push(request.message.clone()); + } + } + args.push(request.name.clone()); + args.push(target); + let result = duct::cmd("git", &args) + .stdout_capture() + .stderr_capture() + .unchecked() + .run()?; + if !result.status.success() { + return Err(GitError::CommandFailed { + status_code: result.status.code(), + stderr: String::from_utf8_lossy(&result.stderr).into_owned(), + }); + } + self.get_tag(GetTagRequest { + repository: request.repository, + name: request.name, + include_raw: false, + }) + } +} diff --git a/tag/delete_tag.rs b/tag/delete_tag.rs new file mode 100644 index 0000000..4872e8b --- /dev/null +++ b/tag/delete_tag.rs @@ -0,0 +1,29 @@ +use crate::bare::GitBare; +use crate::error::{GitError, GitResult}; +use crate::pb::DeleteTagRequest; + +impl GitBare { + pub fn delete_tag(&self, request: DeleteTagRequest) -> GitResult<()> { + let result = duct::cmd( + "git", + [ + "--git-dir", + self.bare_dir.to_string_lossy().as_ref(), + "tag", + "-d", + &request.name, + ], + ) + .stdout_capture() + .stderr_capture() + .unchecked() + .run()?; + if !result.status.success() { + return Err(GitError::CommandFailed { + status_code: result.status.code(), + stderr: String::from_utf8_lossy(&result.stderr).into_owned(), + }); + } + Ok(()) + } +} diff --git a/tag/get_tag.rs b/tag/get_tag.rs new file mode 100644 index 0000000..49c7f12 --- /dev/null +++ b/tag/get_tag.rs @@ -0,0 +1,63 @@ +use gix::bstr::ByteSlice; + +use crate::bare::GitBare; +use crate::error::GitResult; +use crate::pb::{GetTagRequest, ObjectType, Tag}; + +impl GitBare { + pub fn get_tag(&self, request: GetTagRequest) -> GitResult { + let repo = self.gix_repo()?; + let refname = format!("refs/tags/{}", request.name); + let mut r = repo.find_reference(refname.as_str())?; + let full_ref = r.name().as_bstr().to_string(); + + let raw_target_hex = r.target().try_id().map(|id| id.to_string()); + let peeled_hex = r.peel_to_id()?.to_string(); + let is_annotated = raw_target_hex + .as_ref() + .is_some_and(|raw| *raw != peeled_hex); + + let mut tag = Tag { + name: request.name, + full_ref, + target_oid: Some(self.oid_to_pb(peeled_hex)), + target_type: ObjectType::Commit as i32, + tag_oid: None, + annotated: false, + tagger: None, + message: String::new(), + signature: None, + raw: Vec::new(), + }; + + if is_annotated + && let Some(ref raw_hex) = raw_target_hex + && let Ok(id) = gix::hash::ObjectId::from_hex(raw_hex.as_bytes()) + { + tag.tag_oid = Some(self.oid_to_pb(raw_hex.clone())); + if let Ok(obj) = repo.find_object(id) + && let Ok(tag_obj) = obj.try_into_tag() + { + tag.annotated = true; + if let Ok(Some(tagger)) = tag_obj.tagger() { + tag.tagger = Some(crate::pb::Signature { + identity: Some(crate::pb::Identity { + name: tagger.name.to_string(), + email: tagger.email.to_string(), + }), + when: None, + timezone_offset: 0, + }); + } + if let Ok(decoded) = tag_obj.decode() { + tag.message = String::from_utf8_lossy(decoded.message.trim()).into_owned(); + } + if request.include_raw { + tag.raw = tag_obj.data.clone(); + } + } + } + + Ok(tag) + } +} diff --git a/tag/list_tags.rs b/tag/list_tags.rs new file mode 100644 index 0000000..d675317 --- /dev/null +++ b/tag/list_tags.rs @@ -0,0 +1,41 @@ +use crate::bare::GitBare; +use crate::error::GitResult; +use crate::paginate; +use crate::pb::{ListTagsRequest, ListTagsResponse, Tag}; + +impl GitBare { + pub fn list_tags(&self, request: ListTagsRequest) -> GitResult { + let repo = self.gix_repo()?; + let mut tags = Vec::new(); + for r in repo.references()?.tags()? { + let mut r = r.map_err(|e| crate::error::GitError::Gix(e.to_string()))?; + let name = r.name().shorten().to_string(); + if !request.pattern.is_empty() && !name.contains(&request.pattern) { + continue; + } + let hex = r + .peel_to_id() + .ok() + .map(|id| id.to_string()) + .unwrap_or_default(); + tags.push(Tag { + name: name.clone(), + full_ref: r.name().to_string(), + target_oid: Some(self.oid_to_pb(hex)), + target_type: crate::pb::ObjectType::Commit as i32, + tag_oid: None, + annotated: false, + tagger: None, + message: String::new(), + signature: None, + raw: Vec::new(), + }); + } + paginate::apply_sort(&mut tags, request.sort_direction); + let (tags, page_info) = paginate::paginate(&tags, request.pagination.as_ref()); + Ok(ListTagsResponse { + tags, + page_info: Some(page_info), + }) + } +} diff --git a/tag/mod.rs b/tag/mod.rs new file mode 100644 index 0000000..61127b6 --- /dev/null +++ b/tag/mod.rs @@ -0,0 +1,5 @@ +pub mod create_tag; +pub mod delete_tag; +pub mod get_tag; +pub mod list_tags; +pub mod verify_tag; diff --git a/tag/verify_tag.rs b/tag/verify_tag.rs new file mode 100644 index 0000000..b2908c7 --- /dev/null +++ b/tag/verify_tag.rs @@ -0,0 +1,35 @@ +use crate::bare::GitBare; +use crate::error::GitResult; +use crate::pb::{VerifiedSignature, VerifyTagRequest}; + +impl GitBare { + pub fn verify_tag(&self, request: VerifyTagRequest) -> GitResult { + let result = duct::cmd( + "git", + [ + "--git-dir", + self.bare_dir.to_string_lossy().as_ref(), + "tag", + "-v", + &request.name, + ], + ) + .stdout_capture() + .stderr_capture() + .unchecked() + .run()?; + let verified = result.status.success(); + Ok(VerifiedSignature { + verified, + reason: if verified { + crate::pb::verified_signature::Reason::Valid as i32 + } else { + crate::pb::verified_signature::Reason::GpgverifyError as i32 + }, + signature: String::new(), + payload: String::new(), + key_fingerprint: String::new(), + signer: String::new(), + }) + } +} diff --git a/tests/archive_test.rs b/tests/archive_test.rs new file mode 100644 index 0000000..469d9fd --- /dev/null +++ b/tests/archive_test.rs @@ -0,0 +1,169 @@ +mod common; + +use gitks::pb::*; + +#[test] +fn test_get_archive_tar() { + let (_dir, gb) = common::setup_bare_repo(); + let chunks = gb + .get_archive(ArchiveRequest { + repository: None, + treeish: Some(ObjectSelector { + selector: Some(object_selector::Selector::Revision(ObjectName { + revision: "main".into(), + })), + }), + options: Some(ArchiveOptions { + format: archive_options::Format::ArchiveFormatTar as i32, + ..Default::default() + }), + }) + .expect("get_archive tar"); + + assert!(!chunks.is_empty(), "should produce archive data"); + let total_size: usize = chunks.iter().map(|c| c.data.len()).sum(); + assert!(total_size > 0, "archive should not be empty"); +} + +#[test] +fn test_get_archive_zip() { + let (_dir, gb) = common::setup_bare_repo(); + let chunks = gb + .get_archive(ArchiveRequest { + repository: None, + treeish: Some(ObjectSelector { + selector: Some(object_selector::Selector::Revision(ObjectName { + revision: "main".into(), + })), + }), + options: Some(ArchiveOptions { + format: archive_options::Format::ArchiveFormatZip as i32, + ..Default::default() + }), + }) + .expect("get_archive zip"); + + assert!(!chunks.is_empty()); + let data = &chunks[0].data; + assert!( + data.starts_with(b"PK"), + "zip archive should start with PK magic bytes" + ); +} + +#[test] +fn test_list_archive_entries() { + let (_dir, gb) = common::setup_bare_repo(); + let result = gb + .list_archive_entries(ListArchiveEntriesRequest { + repository: None, + treeish: Some(ObjectSelector { + selector: Some(object_selector::Selector::Revision(ObjectName { + revision: "main".into(), + })), + }), + pathspec: vec![], + pagination: None, + }) + .expect("list_archive_entries"); + + assert!(!result.entries.is_empty(), "should list entries"); + let paths: Vec<&str> = result.entries.iter().map(|e| e.path.as_str()).collect(); + assert!( + paths.iter().any(|p| p.contains("README.md")), + "should include README.md, got: {:?}", + paths + ); +} + +#[test] +fn test_get_archive_with_prefix() { + let (_dir, gb) = common::setup_bare_repo(); + let chunks = gb + .get_archive(ArchiveRequest { + repository: None, + treeish: Some(ObjectSelector { + selector: Some(object_selector::Selector::Revision(ObjectName { + revision: "main".into(), + })), + }), + options: Some(ArchiveOptions { + format: archive_options::Format::ArchiveFormatTar as i32, + prefix: "project/".into(), + ..Default::default() + }), + }) + .expect("get_archive with prefix"); + + assert!(!chunks.is_empty()); +} + +#[test] +fn test_fsck_clean_repo() { + let (_dir, gb) = common::setup_bare_repo(); + let result = gb + .fsck(FsckRequest { + repository: None, + strict: false, + connectivity_only: false, + }) + .expect("fsck"); + assert!(result.ok); + assert!(result.errors.is_empty()); +} + +#[test] +fn test_list_packfiles() { + let (_dir, gb) = common::setup_bare_repo(); + + duct::cmd( + "git", + [ + "--git-dir", + gb.bare_dir.to_string_lossy().as_ref(), + "gc", + "--aggressive", + ], + ) + .run() + .expect("git gc"); + + let result = gb + .list_packfiles(ListPackfilesRequest { + repository: None, + pagination: None, + }) + .expect("list_packfiles"); + + assert!( + !result.packfiles.is_empty(), + "bare repo should have packfiles after gc" + ); + for pf in &result.packfiles { + assert!(pf.size_bytes > 0, "packfile should have size"); + assert!(pf.name.ends_with(".pack")); + } +} + +#[test] +fn test_advertise_refs() { + let (_dir, gb) = common::setup_bare_repo(); + let result = gb + .advertise_refs(AdvertiseRefsRequest { + repository: None, + protocol: None, + service: String::new(), + }) + .expect("advertise_refs"); + + assert!(!result.references.is_empty(), "should have refs"); + let ref_names: Vec<&str> = result.references.iter().map(|r| r.name.as_str()).collect(); + assert!( + ref_names.iter().any(|n| n.contains("refs/heads/main")), + "should include main branch ref" + ); + assert!( + !result.capabilities.is_empty(), + "should advertise capabilities" + ); +} diff --git a/tests/blame_test.rs b/tests/blame_test.rs new file mode 100644 index 0000000..5c70388 --- /dev/null +++ b/tests/blame_test.rs @@ -0,0 +1,132 @@ +mod common; + +use gitks::pb::*; + +#[test] +fn test_blame_basic() { + let (_dir, gb) = common::setup_bare_repo(); + let result = gb + .blame(BlameRequest { + repository: None, + revision: Some(ObjectSelector { + selector: Some(object_selector::Selector::Revision(ObjectName { + revision: "main".into(), + })), + }), + path: "README.md".into(), + range: None, + options: None, + pagination: None, + }) + .expect("blame"); + + assert!(!result.hunks.is_empty(), "should have blame hunks"); + for hunk in &result.hunks { + assert!(hunk.commit.is_some(), "each hunk should have a commit"); + let commit = hunk.commit.as_ref().unwrap(); + assert!(commit.oid.is_some(), "commit should have oid"); + assert!(!commit.oid.as_ref().unwrap().hex.is_empty()); + assert!(hunk.line_count > 0, "hunk should have lines"); + assert!(!hunk.lines.is_empty(), "hunk should have parsed lines"); + } +} + +#[test] +fn test_blame_line_content() { + let (_dir, gb) = common::setup_bare_repo(); + let result = gb + .blame(BlameRequest { + repository: None, + revision: Some(ObjectSelector { + selector: Some(object_selector::Selector::Revision(ObjectName { + revision: "main".into(), + })), + }), + path: "README.md".into(), + range: None, + options: None, + pagination: None, + }) + .expect("blame"); + + let all_lines: Vec = result + .hunks + .iter() + .flat_map(|h| h.lines.iter()) + .map(|l| String::from_utf8_lossy(&l.content).to_string()) + .collect(); + + assert!( + all_lines.iter().any(|l| l.contains("# Test")), + "should contain file content, got: {:?}", + all_lines + ); +} + +#[test] +fn test_blame_with_range() { + let (_dir, gb) = common::setup_bare_repo(); + let result = gb + .blame(BlameRequest { + repository: None, + revision: Some(ObjectSelector { + selector: Some(object_selector::Selector::Revision(ObjectName { + revision: "main".into(), + })), + }), + path: "README.md".into(), + range: Some(LineRange { start: 1, end: 1 }), + options: None, + pagination: None, + }) + .expect("blame with range"); + + assert!(!result.hunks.is_empty(), "should have hunks for range"); +} + +#[test] +fn test_blame_author_info() { + let (_dir, gb) = common::setup_bare_repo(); + let result = gb + .blame(BlameRequest { + repository: None, + revision: Some(ObjectSelector { + selector: Some(object_selector::Selector::Revision(ObjectName { + revision: "main".into(), + })), + }), + path: "README.md".into(), + range: None, + options: None, + pagination: None, + }) + .expect("blame"); + + let hunk = &result.hunks[0]; + let commit = hunk.commit.as_ref().unwrap(); + if let Some(ref author) = commit.author { + if let Some(ref id) = author.identity { + assert_eq!(id.name, "Test", "author name should match"); + assert_eq!(id.email, "test@example.com"); + } + } +} + +#[test] +fn test_blame_nonexistent_file() { + let (_dir, gb) = common::setup_bare_repo(); + let result = gb.blame(BlameRequest { + repository: None, + revision: Some(ObjectSelector { + selector: Some(object_selector::Selector::Revision(ObjectName { + revision: "main".into(), + })), + }), + path: "nonexistent.txt".into(), + range: None, + options: None, + pagination: None, + }); + + assert!(result.is_err(), "blame on nonexistent file should fail"); +} diff --git a/tests/branch_test.rs b/tests/branch_test.rs new file mode 100644 index 0000000..4e2fed2 --- /dev/null +++ b/tests/branch_test.rs @@ -0,0 +1,200 @@ +mod common; + +use gitks::pb::*; + +#[test] +fn test_list_branches() { + let (_dir, gb) = common::setup_bare_repo(); + let result = gb + .list_branches(ListBranchesRequest { + repository: None, + pattern: String::new(), + merged_into_head: false, + not_merged_into_head: false, + pagination: None, + sort_direction: 0, + }) + .expect("list_branches"); + let names: Vec = result.branches.iter().map(|b| b.name.clone()).collect(); + assert!(names.contains(&"feature".to_string())); + assert!(names.contains(&"main".to_string())); + assert!(result.branches.len() >= 2); +} + +#[test] +fn test_list_branches_merged_filter() { + let (_dir, gb) = common::setup_bare_repo(); + + let merged = gb + .list_branches(ListBranchesRequest { + repository: None, + pattern: String::new(), + merged_into_head: true, + not_merged_into_head: false, + pagination: None, + sort_direction: 0, + }) + .expect("list_branches merged"); + + let not_merged = gb + .list_branches(ListBranchesRequest { + repository: None, + pattern: String::new(), + merged_into_head: false, + not_merged_into_head: true, + pagination: None, + sort_direction: 0, + }) + .expect("list_branches not merged"); + + let merged_names: Vec<&str> = merged.branches.iter().map(|b| b.name.as_str()).collect(); + let not_merged_names: Vec<&str> = not_merged + .branches + .iter() + .map(|b| b.name.as_str()) + .collect(); + + assert!( + merged_names.contains(&"main"), + "main should be merged into HEAD, got: {:?}", + merged_names + ); + assert!( + not_merged_names.contains(&"feature"), + "feature should NOT be merged into HEAD, got: {:?}", + not_merged_names + ); +} + +#[test] +fn test_get_branch() { + let (_dir, gb) = common::setup_bare_repo(); + let branch = gb + .get_branch(GetBranchRequest { + repository: None, + name: "feature".into(), + }) + .expect("get_branch"); + assert_eq!(branch.full_ref, "refs/heads/feature"); + let oid = branch.target_oid.unwrap(); + assert!(!oid.value.is_empty()); + assert_eq!(oid.value.len(), 20); + assert_eq!(oid.hex.len(), 40); +} + +#[test] +fn test_branch_pagination() { + let (_dir, gb) = common::setup_bare_repo(); + let result = gb + .list_branches(ListBranchesRequest { + repository: None, + pattern: String::new(), + merged_into_head: false, + not_merged_into_head: false, + pagination: Some(Pagination { + page_size: 1, + page_token: String::new(), + }), + sort_direction: 0, + }) + .expect("list_branches page 1"); + let page_info = result.page_info.unwrap(); + assert_eq!(result.branches.len(), 1); + assert!(page_info.has_next_page); + + let result2 = gb + .list_branches(ListBranchesRequest { + repository: None, + pattern: String::new(), + merged_into_head: false, + not_merged_into_head: false, + pagination: Some(Pagination { + page_size: 1, + page_token: page_info.next_page_token, + }), + sort_direction: 0, + }) + .expect("list_branches page 2"); + assert!(!result2.branches.is_empty()); + assert_ne!(result.branches[0].name, result2.branches[0].name); +} + +#[test] +fn test_create_and_delete_branch() { + let (_dir, gb) = common::setup_bare_repo(); + let branch = gb + .create_branch(CreateBranchRequest { + repository: None, + name: "new-branch".into(), + start_point: Some(ObjectSelector { + selector: Some(object_selector::Selector::Revision(ObjectName { + revision: "main".into(), + })), + }), + force: false, + }) + .expect("create_branch"); + assert_eq!(branch.name, "new-branch"); + + gb.delete_branch(DeleteBranchRequest { + repository: None, + name: "new-branch".into(), + force: true, + }) + .expect("delete_branch"); + + let result = gb.get_branch(GetBranchRequest { + repository: None, + name: "new-branch".into(), + }); + assert!(result.is_err(), "deleted branch should not exist"); +} + +#[test] +fn test_rename_branch() { + let (_dir, gb) = common::setup_bare_repo(); + gb.create_branch(CreateBranchRequest { + repository: None, + name: "to-rename".into(), + start_point: Some(ObjectSelector { + selector: Some(object_selector::Selector::Revision(ObjectName { + revision: "main".into(), + })), + }), + force: false, + }) + .expect("create branch for rename"); + + let renamed = gb + .rename_branch(RenameBranchRequest { + repository: None, + old_name: "to-rename".into(), + new_name: "renamed".into(), + }) + .expect("rename_branch"); + assert_eq!(renamed.name, "renamed"); + + let old = gb.get_branch(GetBranchRequest { + repository: None, + name: "to-rename".into(), + }); + assert!(old.is_err(), "old branch name should not exist"); +} + +#[test] +fn test_compare_branch() { + let (_dir, gb) = common::setup_bare_repo(); + let result = gb + .compare_branch(CompareBranchRequest { + repository: None, + source_branch: "feature".into(), + target_branch: "main".into(), + }) + .expect("compare_branch"); + + assert!( + result.ahead_by > 0 || result.behind_by > 0, + "branches should differ" + ); + assert!(result.merge_base.is_some(), "should find merge base"); +} diff --git a/tests/commit_test.rs b/tests/commit_test.rs new file mode 100644 index 0000000..b8bff62 --- /dev/null +++ b/tests/commit_test.rs @@ -0,0 +1,469 @@ +mod common; + +use gitks::pb::*; + +#[test] +fn test_get_commit_with_author() { + let (_dir, gb) = common::setup_bare_repo(); + let commit = gb + .get_commit(GetCommitRequest { + repository: None, + revision: Some(ObjectSelector { + selector: Some(object_selector::Selector::Revision(ObjectName { + revision: "main".into(), + })), + }), + include_stats: false, + include_raw: false, + }) + .expect("get_commit"); + + assert!(commit.author.is_some(), "author must be populated"); + let author = commit.author.as_ref().unwrap(); + assert!( + author.identity.is_some(), + "author identity must be populated" + ); + let id = author.identity.as_ref().unwrap(); + assert_eq!(id.name, "Test", "author name should be 'Test'"); + assert_eq!(id.email, "test@example.com"); + + assert!(commit.committer.is_some(), "committer must be populated"); + assert!( + commit.authored_at.is_some(), + "authored_at must be populated" + ); + assert!( + commit.committed_at.is_some(), + "committed_at must be populated" + ); + assert!( + commit.authored_at.as_ref().unwrap().seconds > 0, + "timestamp must be non-zero" + ); +} + +#[test] +fn test_get_commit_subject_body() { + let (_dir, gb) = common::setup_bare_repo(); + let commit = gb + .get_commit(GetCommitRequest { + repository: None, + revision: Some(ObjectSelector { + selector: Some(object_selector::Selector::Revision(ObjectName { + revision: "main~2".into(), + })), + }), + include_stats: false, + include_raw: false, + }) + .expect("get_commit"); + + assert_eq!(commit.subject, "second commit"); + assert!(!commit.message.is_empty()); + assert!(commit.oid.is_some()); + assert!(!commit.parent_oids.is_empty()); +} + +#[test] +fn test_get_commit_with_raw() { + let (_dir, gb) = common::setup_bare_repo(); + let commit = gb + .get_commit(GetCommitRequest { + repository: None, + revision: Some(ObjectSelector { + selector: Some(object_selector::Selector::Revision(ObjectName { + revision: "main".into(), + })), + }), + include_stats: false, + include_raw: true, + }) + .expect("get_commit with raw"); + + assert!( + !commit.raw.is_empty(), + "raw data must be present when requested" + ); + let raw_str = String::from_utf8_lossy(&commit.raw); + assert!(raw_str.contains("tree"), "raw should contain tree line"); + assert!(raw_str.contains("author"), "raw should contain author line"); +} + +#[test] +fn test_list_commits_with_pagination() { + let (_dir, gb) = common::setup_bare_repo(); + let page1 = gb + .list_commits(ListCommitsRequest { + repository: None, + revision: Some(ObjectSelector { + selector: Some(object_selector::Selector::Revision(ObjectName { + revision: "main".into(), + })), + }), + path: String::new(), + since: None, + until: None, + first_parent: false, + all: false, + reverse: false, + max_parents: 0, + min_parents: 0, + pagination: Some(Pagination { + page_size: 2, + page_token: String::new(), + }), + }) + .expect("list_commits page 1"); + assert_eq!(page1.commits.len(), 2); + let pi = page1.page_info.unwrap(); + assert!(pi.has_next_page); + + let page2 = gb + .list_commits(ListCommitsRequest { + repository: None, + revision: Some(ObjectSelector { + selector: Some(object_selector::Selector::Revision(ObjectName { + revision: "main".into(), + })), + }), + path: String::new(), + since: None, + until: None, + first_parent: false, + all: false, + reverse: false, + max_parents: 0, + min_parents: 0, + pagination: Some(Pagination { + page_size: 2, + page_token: pi.next_page_token, + }), + }) + .expect("list_commits page 2"); + assert!(!page2.commits.is_empty()); + assert_ne!(page1.commits[0].oid, page2.commits[0].oid); +} + +#[test] +fn test_get_commit_ancestors_pagination() { + let (_dir, gb) = common::setup_bare_repo(); + let page1 = gb + .get_commit_ancestors(GetCommitAncestorsRequest { + repository: None, + revision: Some(ObjectSelector { + selector: Some(object_selector::Selector::Revision(ObjectName { + revision: "main".into(), + })), + }), + first_parent: false, + pagination: Some(Pagination { + page_size: 2, + page_token: String::new(), + }), + }) + .expect("ancestors page 1"); + + assert_eq!(page1.commits.len(), 2); + let pi = page1.page_info.unwrap(); + assert!(pi.has_next_page, "should have next page"); + assert!( + !pi.next_page_token.is_empty(), + "next_page_token must be set" + ); + + let page2 = gb + .get_commit_ancestors(GetCommitAncestorsRequest { + repository: None, + revision: Some(ObjectSelector { + selector: Some(object_selector::Selector::Revision(ObjectName { + revision: "main".into(), + })), + }), + first_parent: false, + pagination: Some(Pagination { + page_size: 2, + page_token: pi.next_page_token, + }), + }) + .expect("ancestors page 2"); + + assert!(!page2.commits.is_empty(), "page 2 should have commits"); + assert_ne!( + page1.commits[0].oid.as_ref().unwrap().hex, + page2.commits[0].oid.as_ref().unwrap().hex, + ); +} + +#[test] +fn test_compare_commits() { + let (_dir, gb) = common::setup_bare_repo(); + let result = gb + .compare_commits(CompareCommitsRequest { + repository: None, + base: Some(ObjectSelector { + selector: Some(object_selector::Selector::Revision(ObjectName { + revision: "feature".into(), + })), + }), + head: Some(ObjectSelector { + selector: Some(object_selector::Selector::Revision(ObjectName { + revision: "main".into(), + })), + }), + straight: false, + first_parent: false, + pagination: Some(Pagination { + page_size: 100, + page_token: String::new(), + }), + }) + .expect("compare_commits"); + + assert!(!result.commits.is_empty()); + assert!(result.merge_base.is_some()); + let stats = result.stats.unwrap(); + assert!(stats.additions > 0); +} + +#[test] +fn test_create_commit_and_cherry_pick() { + let (_dir, gb) = common::setup_bare_repo(); + + let created = gb + .create_commit(CreateCommitRequest { + repository: None, + branch: "feature".into(), + message: "cherry-pick source".into(), + author: Some(Signature { + identity: Some(Identity { + name: "Author".into(), + email: "author@test.com".into(), + }), + ..Default::default() + }), + committer: Some(Signature { + identity: Some(Identity { + name: "Committer".into(), + email: "committer@test.com".into(), + }), + ..Default::default() + }), + actions: vec![CreateCommitAction { + action: create_commit_action::Action::CreateCommitActionCreate as i32, + file_path: "cp_file.txt".into(), + previous_path: String::new(), + content: b"cherry pick me".to_vec(), + encoding: String::new(), + executable: false, + last_commit_oid: None, + }], + start_revision: Some(ObjectSelector { + selector: Some(object_selector::Selector::Revision(ObjectName { + revision: "feature".into(), + })), + }), + force: false, + trailers: vec![], + }) + .expect("create_commit for cherry-pick source"); + + let source_oid = created + .commit + .as_ref() + .unwrap() + .oid + .as_ref() + .unwrap() + .hex + .clone(); + + let cp_result = gb + .cherry_pick_commit(CherryPickCommitRequest { + repository: None, + commit: Some(ObjectSelector { + selector: Some(object_selector::Selector::Revision(ObjectName { + revision: source_oid.clone(), + })), + }), + branch: "main".into(), + committer: Some(Signature { + identity: Some(Identity { + name: "CP Committer".into(), + email: "cp@test.com".into(), + }), + ..Default::default() + }), + message: String::new(), + mainline: 0, + }) + .expect("cherry_pick_commit"); + + let cp_commit = cp_result.commit.unwrap(); + assert_eq!(cp_commit.subject, "cherry-pick source"); + + let blob = gb + .get_blob(GetBlobRequest { + repository: None, + revision: Some(ObjectSelector { + selector: Some(object_selector::Selector::Revision(ObjectName { + revision: "main".into(), + })), + }), + path: "cp_file.txt".into(), + oid: None, + max_bytes: 0, + }) + .expect("get_blob after cherry-pick"); + assert_eq!(blob.data, b"cherry pick me"); +} + +#[test] +fn test_cherry_pick_root_commit() { + let (dir, gb) = common::setup_bare_repo(); + let work_dir = dir.path().join("work"); + + common::run(&work_dir, &["checkout", "--orphan", "root-source"]); + common::run(&work_dir, &["rm", "-rf", "."]); + std::fs::write(work_dir.join("root_only.txt"), "from root\n").unwrap(); + common::run(&work_dir, &["add", "."]); + common::run(&work_dir, &["commit", "-m", "standalone root"]); + common::run(&work_dir, &["push", "-f", "origin", "root-source"]); + + let root_oid = common::run_git(&work_dir, &["rev-parse", "root-source"]) + .stdout_capture() + .stderr_capture() + .run() + .expect("find root commit") + .stdout; + let root_oid = String::from_utf8(root_oid).unwrap().trim().to_string(); + + gb.cherry_pick_commit(CherryPickCommitRequest { + repository: None, + commit: Some(ObjectSelector { + selector: Some(object_selector::Selector::Revision(ObjectName { + revision: root_oid, + })), + }), + branch: "feature".into(), + committer: None, + message: String::new(), + mainline: 0, + }) + .expect("cherry_pick_commit root"); + + let blob = gb + .get_blob(GetBlobRequest { + repository: None, + revision: Some(ObjectSelector { + selector: Some(object_selector::Selector::Revision(ObjectName { + revision: "feature".into(), + })), + }), + path: "root_only.txt".into(), + oid: None, + max_bytes: 0, + }) + .expect("get root file after cherry-pick"); + assert_eq!(blob.data, b"from root\n"); +} + +#[test] +fn test_revert_commit() { + let (_dir, gb) = common::setup_bare_repo(); + + let created = gb + .create_commit(CreateCommitRequest { + repository: None, + branch: "main".into(), + message: "to be reverted".into(), + author: None, + committer: None, + actions: vec![CreateCommitAction { + action: create_commit_action::Action::CreateCommitActionCreate as i32, + file_path: "revert_me.txt".into(), + previous_path: String::new(), + content: b"will be reverted".to_vec(), + encoding: String::new(), + executable: false, + last_commit_oid: None, + }], + start_revision: Some(ObjectSelector { + selector: Some(object_selector::Selector::Revision(ObjectName { + revision: "main".into(), + })), + }), + force: false, + trailers: vec![], + }) + .expect("create_commit"); + + let to_revert = created + .commit + .as_ref() + .unwrap() + .oid + .as_ref() + .unwrap() + .hex + .clone(); + + let revert_result = gb + .revert_commit(RevertCommitRequest { + repository: None, + commit: Some(ObjectSelector { + selector: Some(object_selector::Selector::Revision(ObjectName { + revision: to_revert, + })), + }), + branch: "main".into(), + committer: None, + message: String::new(), + }) + .expect("revert_commit"); + + let revert_commit = revert_result.commit.unwrap(); + assert!( + revert_commit.subject.starts_with("Revert"), + "subject should start with 'Revert', got: {}", + revert_commit.subject + ); + + let blob_result = gb.get_blob(GetBlobRequest { + repository: None, + revision: Some(ObjectSelector { + selector: Some(object_selector::Selector::Revision(ObjectName { + revision: "main".into(), + })), + }), + path: "revert_me.txt".into(), + oid: None, + max_bytes: 0, + }); + assert!( + blob_result.is_err(), + "revert_me.txt should be deleted after revert" + ); +} + +#[test] +fn test_oid_binary_encoding() { + let (_dir, gb) = common::setup_bare_repo(); + let commit = gb + .get_commit(GetCommitRequest { + repository: None, + revision: Some(ObjectSelector { + selector: Some(object_selector::Selector::Revision(ObjectName { + revision: "main".into(), + })), + }), + include_stats: false, + include_raw: false, + }) + .expect("get_commit"); + let oid = commit.oid.unwrap(); + assert_eq!(oid.value.len(), 20); + assert_eq!(oid.hex.len(), 40); + let hex_from_bytes: String = oid.value.iter().map(|b| format!("{b:02x}")).collect(); + assert_eq!(hex_from_bytes, oid.hex); +} diff --git a/tests/common/mod.rs b/tests/common/mod.rs new file mode 100644 index 0000000..9fab786 --- /dev/null +++ b/tests/common/mod.rs @@ -0,0 +1,167 @@ +use gitks::bare::GitBare; + +pub fn run_git(work_dir: &std::path::Path, args: &[&str]) -> duct::Expression { + duct::cmd("git", { + let work_str = work_dir.to_string_lossy().into_owned(); + let mut v: Vec = vec!["-C".into(), work_str]; + v.extend(args.iter().map(|s| s.to_string())); + v + }) + .env("GIT_AUTHOR_NAME", "Test") + .env("GIT_AUTHOR_EMAIL", "test@example.com") + .env("GIT_COMMITTER_NAME", "Test") + .env("GIT_COMMITTER_EMAIL", "test@example.com") +} + +pub fn run(work_dir: &std::path::Path, args: &[&str]) { + let result = run_git(work_dir, args) + .stdout_capture() + .stderr_capture() + .unchecked() + .run() + .unwrap(); + assert!( + result.status.success(), + "git {} failed: {}", + args.join(" "), + String::from_utf8_lossy(&result.stderr) + ); +} + +pub fn setup_bare_repo() -> (tempfile::TempDir, GitBare) { + let dir = tempfile::tempdir().expect("create temp dir"); + let bare_dir = dir.path().join("test-repo"); + + duct::cmd( + "git", + ["init", "--bare", bare_dir.to_string_lossy().as_ref()], + ) + .run() + .expect("git init --bare"); + + let work_dir = dir.path().join("work"); + duct::cmd( + "git", + [ + "clone", + bare_dir.to_string_lossy().as_ref(), + work_dir.to_string_lossy().as_ref(), + ], + ) + .run() + .expect("clone"); + + run(&work_dir, &["checkout", "-b", "main"]); + + std::fs::write(work_dir.join("README.md"), "# Test\n").unwrap(); + run(&work_dir, &["add", "."]); + run(&work_dir, &["commit", "-m", "initial commit"]); + + run(&work_dir, &["branch", "feature"]); + + std::fs::write(work_dir.join("src.txt"), "source\n").unwrap(); + run(&work_dir, &["add", "."]); + run(&work_dir, &["commit", "-m", "second commit"]); + + std::fs::write(work_dir.join("README.md"), "# Test\n\nUpdated.\n").unwrap(); + run(&work_dir, &["add", "."]); + run(&work_dir, &["commit", "-m", "third commit"]); + + std::fs::create_dir_all(work_dir.join("src/lib")).unwrap(); + std::fs::write(work_dir.join("src/lib/mod.rs"), "pub fn hello() {}\n").unwrap(); + run(&work_dir, &["add", "."]); + run(&work_dir, &["commit", "-m", "add nested file"]); + + run(&work_dir, &["tag", "v0.1.0"]); + + run( + &work_dir, + &["push", "-f", "origin", "main:main", "feature:feature"], + ); + run( + &work_dir, + &["push", "-f", "origin", "refs/tags/v0.1.0:refs/tags/v0.1.0"], + ); + + duct::cmd( + "git", + [ + "--git-dir", + bare_dir.to_string_lossy().as_ref(), + "symbolic-ref", + "HEAD", + "refs/heads/main", + ], + ) + .run() + .expect("set HEAD to main"); + + (dir, GitBare { bare_dir }) +} + +pub fn setup_bare_repo_with_conflict() -> (tempfile::TempDir, GitBare) { + let dir = tempfile::tempdir().expect("create temp dir"); + let bare_dir = dir.path().join("test-repo"); + + duct::cmd( + "git", + ["init", "--bare", bare_dir.to_string_lossy().as_ref()], + ) + .run() + .expect("git init --bare"); + + let work_dir = dir.path().join("work"); + duct::cmd( + "git", + [ + "clone", + bare_dir.to_string_lossy().as_ref(), + work_dir.to_string_lossy().as_ref(), + ], + ) + .run() + .expect("clone"); + + run(&work_dir, &["checkout", "-b", "main"]); + std::fs::write(work_dir.join("file.txt"), "base content\n").unwrap(); + run(&work_dir, &["add", "."]); + run(&work_dir, &["commit", "-m", "base commit"]); + + run(&work_dir, &["checkout", "-b", "branch-a"]); + std::fs::write(work_dir.join("file.txt"), "branch A content\n").unwrap(); + run(&work_dir, &["add", "."]); + run(&work_dir, &["commit", "-m", "branch A change"]); + + run(&work_dir, &["checkout", "main"]); + run(&work_dir, &["checkout", "-b", "branch-b"]); + std::fs::write(work_dir.join("file.txt"), "branch B content\n").unwrap(); + run(&work_dir, &["add", "."]); + run(&work_dir, &["commit", "-m", "branch B change"]); + + run( + &work_dir, + &[ + "push", + "-f", + "origin", + "main:main", + "branch-a:branch-a", + "branch-b:branch-b", + ], + ); + + duct::cmd( + "git", + [ + "--git-dir", + bare_dir.to_string_lossy().as_ref(), + "symbolic-ref", + "HEAD", + "refs/heads/main", + ], + ) + .run() + .expect("set HEAD to main"); + + (dir, GitBare { bare_dir }) +} diff --git a/tests/diff_test.rs b/tests/diff_test.rs new file mode 100644 index 0000000..9255ba7 --- /dev/null +++ b/tests/diff_test.rs @@ -0,0 +1,236 @@ +mod common; + +use gitks::pb::*; + +#[test] +fn test_get_diff() { + let (_dir, gb) = common::setup_bare_repo(); + let result = gb + .get_diff(GetDiffRequest { + repository: None, + base: Some(ObjectSelector { + selector: Some(object_selector::Selector::Revision(ObjectName { + revision: "main~3".into(), + })), + }), + head: Some(ObjectSelector { + selector: Some(object_selector::Selector::Revision(ObjectName { + revision: "main".into(), + })), + }), + options: None, + pagination: None, + }) + .expect("get_diff"); + + assert!(!result.files.is_empty()); + let paths: Vec<&str> = result.files.iter().map(|f| f.new_path.as_str()).collect(); + assert!( + paths.iter().any(|p| p.contains("src.txt")), + "should include src.txt, got: {:?}", + paths + ); + let stats = result.stats.unwrap(); + assert!(stats.additions > 0 || stats.changed_files > 0); +} + +#[test] +fn test_get_diff_with_patch() { + let (_dir, gb) = common::setup_bare_repo(); + let result = gb + .get_diff(GetDiffRequest { + repository: None, + base: Some(ObjectSelector { + selector: Some(object_selector::Selector::Revision(ObjectName { + revision: "main~1".into(), + })), + }), + head: Some(ObjectSelector { + selector: Some(object_selector::Selector::Revision(ObjectName { + revision: "main".into(), + })), + }), + options: Some(DiffOptions { + include_patch: true, + context_lines: 3, + ..Default::default() + }), + pagination: None, + }) + .expect("get_diff with patch"); + + assert!(!result.files.is_empty()); + for file in &result.files { + if !file.binary { + assert!( + !file.patch.is_empty(), + "non-binary file should have patch: {}", + file.new_path + ); + } + } +} + +#[test] +fn test_get_diff_with_rename_detection() { + let (_dir, gb) = common::setup_bare_repo(); + + gb.create_commit(CreateCommitRequest { + repository: None, + branch: "main".into(), + message: "rename file".into(), + author: None, + committer: None, + actions: vec![ + CreateCommitAction { + action: create_commit_action::Action::CreateCommitActionCreate as i32, + file_path: "renamed.txt".into(), + previous_path: String::new(), + content: b"source\n".to_vec(), + encoding: String::new(), + executable: false, + last_commit_oid: None, + }, + CreateCommitAction { + action: create_commit_action::Action::CreateCommitActionDelete as i32, + file_path: "src.txt".into(), + previous_path: String::new(), + content: vec![], + encoding: String::new(), + executable: false, + last_commit_oid: None, + }, + ], + start_revision: Some(ObjectSelector { + selector: Some(object_selector::Selector::Revision(ObjectName { + revision: "main".into(), + })), + }), + force: false, + trailers: vec![], + }) + .expect("create rename commit"); + + let result = gb + .get_diff(GetDiffRequest { + repository: None, + base: Some(ObjectSelector { + selector: Some(object_selector::Selector::Revision(ObjectName { + revision: "main~1".into(), + })), + }), + head: Some(ObjectSelector { + selector: Some(object_selector::Selector::Revision(ObjectName { + revision: "main".into(), + })), + }), + options: Some(DiffOptions { + rename_detection: true, + ..Default::default() + }), + pagination: None, + }) + .expect("get_diff with rename detection"); + + let has_rename = result + .files + .iter() + .any(|f| f.change_type == diff_file::ChangeType::DiffFileChangeTypeRenamed as i32); + assert!( + has_rename, + "should detect rename, files: {:?}", + result.files.len() + ); +} + +#[test] +fn test_get_commit_diff_root() { + let (_dir, gb) = common::setup_bare_repo(); + let commits = gb + .list_commits(ListCommitsRequest { + repository: None, + revision: Some(ObjectSelector { + selector: Some(object_selector::Selector::Revision(ObjectName { + revision: "main".into(), + })), + }), + path: String::new(), + since: None, + until: None, + first_parent: false, + all: false, + reverse: true, + max_parents: 0, + min_parents: 0, + pagination: Some(Pagination { + page_size: 1, + page_token: String::new(), + }), + }) + .expect("list_commits for root"); + let root_oid = commits.commits[0].oid.as_ref().unwrap().hex.clone(); + + let result = gb + .get_commit_diff(GetCommitDiffRequest { + repository: None, + commit: Some(ObjectSelector { + selector: Some(object_selector::Selector::Revision(ObjectName { + revision: root_oid, + })), + }), + options: None, + pagination: None, + }) + .expect("get_commit_diff on root"); + + assert!(!result.files.is_empty(), "root commit should have files"); +} + +#[test] +fn test_get_diff_stats() { + let (_dir, gb) = common::setup_bare_repo(); + let stats = gb + .get_diff_stats(GetDiffStatsRequest { + repository: None, + base: Some(ObjectSelector { + selector: Some(object_selector::Selector::Revision(ObjectName { + revision: "main~3".into(), + })), + }), + head: Some(ObjectSelector { + selector: Some(object_selector::Selector::Revision(ObjectName { + revision: "main".into(), + })), + }), + options: None, + }) + .expect("get_diff_stats"); + assert!(stats.additions > 0 || stats.changed_files > 0); +} + +#[test] +fn test_get_patch() { + let (_dir, gb) = common::setup_bare_repo(); + let patches = gb + .get_patch(GetPatchRequest { + repository: None, + base: Some(ObjectSelector { + selector: Some(object_selector::Selector::Revision(ObjectName { + revision: "main~1".into(), + })), + }), + head: Some(ObjectSelector { + selector: Some(object_selector::Selector::Revision(ObjectName { + revision: "main".into(), + })), + }), + options: None, + }) + .expect("get_patch"); + assert!(!patches.is_empty()); + let combined: String = patches + .iter() + .map(|p| String::from_utf8_lossy(&p.data).to_string()) + .collect(); + assert!(combined.contains("diff --git") || combined.contains("@@")); +} diff --git a/tests/integration.rs b/tests/integration.rs new file mode 100644 index 0000000..23a46dc --- /dev/null +++ b/tests/integration.rs @@ -0,0 +1,743 @@ +use gitks::bare::GitBare; +use gitks::pb::{ + CreateCommitAction, CreateCommitRequest, FindFilesRequest, FsckRequest, GetBlobRequest, + GetBranchRequest, GetCommitDiffRequest, GetCommitRequest, GetDiffRequest, GetDiffStatsRequest, + GetPatchRequest, GetTreeRequest, ListBranchesRequest, ListCommitsRequest, ListTagsRequest, + ListTreeRequest, ObjectName, ObjectSelector, Pagination, create_commit_action, object_selector, +}; + +fn run_git(work_dir: &std::path::Path, args: &[&str]) -> duct::Expression { + duct::cmd("git", { + let work_str = work_dir.to_string_lossy().into_owned(); + let mut v: Vec = vec!["-C".into(), work_str]; + v.extend(args.iter().map(|s| s.to_string())); + v + }) + .env("GIT_AUTHOR_NAME", "Test") + .env("GIT_AUTHOR_EMAIL", "test@example.com") + .env("GIT_COMMITTER_NAME", "Test") + .env("GIT_COMMITTER_EMAIL", "test@example.com") +} + +fn run(work_dir: &std::path::Path, args: &[&str]) { + let result = run_git(work_dir, args) + .stdout_capture() + .stderr_capture() + .unchecked() + .run() + .unwrap(); + assert!( + result.status.success(), + "git {} failed: {}", + args.join(" "), + String::from_utf8_lossy(&result.stderr) + ); +} + +/// Create a temporary bare repo with real git history. +fn setup_bare_repo() -> (tempfile::TempDir, GitBare) { + let dir = tempfile::tempdir().expect("create temp dir"); + let bare_dir = dir.path().join("test-repo"); + + duct::cmd( + "git", + ["init", "--bare", bare_dir.to_string_lossy().as_ref()], + ) + .run() + .expect("git init --bare"); + + let work_dir = dir.path().join("work"); + duct::cmd( + "git", + [ + "clone", + bare_dir.to_string_lossy().as_ref(), + work_dir.to_string_lossy().as_ref(), + ], + ) + .run() + .expect("clone"); + + run(&work_dir, &["checkout", "-b", "main"]); + + // Initial commit: README.md + std::fs::write(work_dir.join("README.md"), "# Test\n").unwrap(); + run(&work_dir, &["add", "."]); + run(&work_dir, &["commit", "-m", "initial commit"]); + + // Branch feature from initial + run(&work_dir, &["branch", "feature"]); + + // Second commit: add src.txt + std::fs::write(work_dir.join("src.txt"), "source\n").unwrap(); + run(&work_dir, &["add", "."]); + run(&work_dir, &["commit", "-m", "second commit"]); + + // Third commit: modify README.md + std::fs::write(work_dir.join("README.md"), "# Test\n\nUpdated.\n").unwrap(); + run(&work_dir, &["add", "."]); + run(&work_dir, &["commit", "-m", "third commit"]); + + // Create a subdirectory with nested file + std::fs::create_dir_all(work_dir.join("src/lib")).unwrap(); + std::fs::write(work_dir.join("src/lib/mod.rs"), "pub fn hello() {}\n").unwrap(); + run(&work_dir, &["add", "."]); + run(&work_dir, &["commit", "-m", "add nested file"]); + + run(&work_dir, &["tag", "v0.1.0"]); + + run( + &work_dir, + &["push", "-f", "origin", "main:main", "feature:feature"], + ); + run( + &work_dir, + &["push", "-f", "origin", "refs/tags/v0.1.0:refs/tags/v0.1.0"], + ); + + ( + dir, + GitBare { + bare_dir: bare_dir.clone(), + }, + ) +} + +#[test] +fn test_list_branches() { + let (_dir, gb) = setup_bare_repo(); + let result = gb + .list_branches(ListBranchesRequest { + repository: None, + pattern: String::new(), + merged_into_head: false, + not_merged_into_head: false, + pagination: None, + sort_direction: 0, + }) + .expect("list_branches"); + let names: Vec = result.branches.iter().map(|b| b.name.clone()).collect(); + assert!(names.contains(&"feature".to_string()), "names: {names:?}"); + assert!( + result.branches.len() >= 2, + "got {} branches", + result.branches.len() + ); +} + +#[test] +fn test_get_branch() { + let (_dir, gb) = setup_bare_repo(); + let branch = gb + .get_branch(GetBranchRequest { + repository: None, + name: "feature".into(), + }) + .expect("get_branch"); + assert_eq!(branch.full_ref, "refs/heads/feature"); + let oid = branch.target_oid.unwrap(); + assert!(!oid.value.is_empty(), "oid.value must be binary bytes"); + assert_eq!(oid.value.len(), 20, "SHA-1 binary is 20 bytes"); + assert_eq!(oid.hex.len(), 40, "SHA-1 hex is 40 chars"); +} + +#[test] +fn test_branch_pagination() { + let (_dir, gb) = setup_bare_repo(); + let result = gb + .list_branches(ListBranchesRequest { + repository: None, + pattern: String::new(), + merged_into_head: false, + not_merged_into_head: false, + pagination: Some(Pagination { + page_size: 1, + page_token: String::new(), + }), + sort_direction: 0, + }) + .expect("list_branches"); + let page_info = result.page_info.unwrap(); + assert_eq!(result.branches.len(), 1); + assert!(page_info.has_next_page); + assert!(!page_info.next_page_token.is_empty()); + + // Fetch second page + let result2 = gb + .list_branches(ListBranchesRequest { + repository: None, + pattern: String::new(), + merged_into_head: false, + not_merged_into_head: false, + pagination: Some(Pagination { + page_size: 1, + page_token: page_info.next_page_token, + }), + sort_direction: 0, + }) + .expect("list_branches page 2"); + assert!(!result2.branches.is_empty()); + assert_ne!(result.branches[0].name, result2.branches[0].name); +} + +#[test] +fn test_list_commits() { + let (_dir, gb) = setup_bare_repo(); + let result = gb + .list_commits(ListCommitsRequest { + repository: None, + revision: Some(ObjectSelector { + selector: Some(object_selector::Selector::Revision(ObjectName { + revision: "main".into(), + })), + }), + path: String::new(), + since: None, + until: None, + first_parent: false, + all: false, + reverse: false, + max_parents: 0, + min_parents: 0, + pagination: Some(Pagination { + page_size: 100, + page_token: String::new(), + }), + }) + .expect("list_commits"); + assert!( + result.commits.len() >= 4, + "expected >=4 commits, got {}", + result.commits.len() + ); + // Oid binary encoding check + let first = &result.commits[0]; + let oid = first.oid.as_ref().unwrap(); + assert_eq!(oid.value.len(), 20, "binary OID must be 20 bytes for SHA-1"); + assert_eq!(oid.hex.len(), 40); +} + +#[test] +fn test_list_commits_with_pagination() { + let (_dir, gb) = setup_bare_repo(); + let page1 = gb + .list_commits(ListCommitsRequest { + repository: None, + revision: Some(ObjectSelector { + selector: Some(object_selector::Selector::Revision(ObjectName { + revision: "main".into(), + })), + }), + path: String::new(), + since: None, + until: None, + first_parent: false, + all: false, + reverse: false, + max_parents: 0, + min_parents: 0, + pagination: Some(Pagination { + page_size: 2, + page_token: String::new(), + }), + }) + .expect("list_commits page 1"); + assert_eq!(page1.commits.len(), 2); + let pi = page1.page_info.unwrap(); + assert!(pi.has_next_page); + + let page2 = gb + .list_commits(ListCommitsRequest { + repository: None, + revision: Some(ObjectSelector { + selector: Some(object_selector::Selector::Revision(ObjectName { + revision: "main".into(), + })), + }), + path: String::new(), + since: None, + until: None, + first_parent: false, + all: false, + reverse: false, + max_parents: 0, + min_parents: 0, + pagination: Some(Pagination { + page_size: 2, + page_token: pi.next_page_token, + }), + }) + .expect("list_commits page 2"); + assert!(!page2.commits.is_empty()); + // Page 2 commits should differ from page 1 + assert_ne!(page1.commits[0].oid, page2.commits[0].oid); +} + +#[test] +fn test_get_commit() { + let (_dir, gb) = setup_bare_repo(); + let commit = gb + .get_commit(GetCommitRequest { + repository: None, + revision: Some(ObjectSelector { + selector: Some(object_selector::Selector::Revision(ObjectName { + revision: "main".into(), + })), + }), + include_stats: false, + include_raw: false, + }) + .expect("get_commit"); + assert!(commit.oid.is_some()); + assert_eq!(commit.subject, "add nested file"); + assert!(!commit.parent_oids.is_empty(), "should have parent"); +} + +#[test] +fn test_get_diff() { + let (_dir, gb) = setup_bare_repo(); + let result = gb + .get_diff(GetDiffRequest { + repository: None, + base: Some(ObjectSelector { + selector: Some(object_selector::Selector::Revision(ObjectName { + revision: "main~3".into(), + })), + }), + head: Some(ObjectSelector { + selector: Some(object_selector::Selector::Revision(ObjectName { + revision: "main".into(), + })), + }), + options: None, + pagination: None, + }) + .expect("get_diff"); + + assert!(!result.files.is_empty(), "diff should have changed files"); + + // Check that file paths are populated (not empty strings) + let paths: Vec<&str> = result.files.iter().map(|f| f.new_path.as_str()).collect(); + let has_src = paths.iter().any(|p| p.contains("src.txt")); + assert!(has_src, "should include src.txt in diff, got: {paths:?}"); + + // Stats should not all be zero + let stats = result.stats.unwrap(); + assert!( + stats.additions > 0 || stats.changed_files > 0, + "stats should be non-zero: {stats:?}" + ); +} + +#[test] +fn test_get_diff_with_patch() { + let (_dir, gb) = setup_bare_repo(); + let result = gb + .get_diff(GetDiffRequest { + repository: None, + base: Some(ObjectSelector { + selector: Some(object_selector::Selector::Revision(ObjectName { + revision: "main~1".into(), + })), + }), + head: Some(ObjectSelector { + selector: Some(object_selector::Selector::Revision(ObjectName { + revision: "main".into(), + })), + }), + options: Some(gitks::pb::DiffOptions { + include_patch: true, + context_lines: 3, + ..Default::default() + }), + pagination: None, + }) + .expect("get_diff with patch"); + + assert!(!result.files.is_empty()); + for file in &result.files { + if !file.binary { + assert!( + !file.patch.is_empty(), + "non-binary file should have patch data: {}", + file.new_path + ); + } + } +} + +#[test] +fn test_get_commit_diff_root() { + let (_dir, gb) = setup_bare_repo(); + // Get the root commit (first commit on main) + let commits = gb + .list_commits(ListCommitsRequest { + repository: None, + revision: Some(ObjectSelector { + selector: Some(object_selector::Selector::Revision(ObjectName { + revision: "main".into(), + })), + }), + path: String::new(), + since: None, + until: None, + first_parent: false, + all: false, + reverse: true, + max_parents: 0, + min_parents: 0, + pagination: Some(Pagination { + page_size: 1, + page_token: String::new(), + }), + }) + .expect("list_commits for root"); + let root_oid = commits.commits[0].oid.as_ref().unwrap().hex.clone(); + + // get_commit_diff on root commit should not fail + let result = gb + .get_commit_diff(GetCommitDiffRequest { + repository: None, + commit: Some(ObjectSelector { + selector: Some(object_selector::Selector::Revision(ObjectName { + revision: root_oid, + })), + }), + options: None, + pagination: None, + }) + .expect("get_commit_diff on root"); + + assert!( + !result.files.is_empty(), + "root commit diff should show added files" + ); +} + +#[test] +fn test_get_diff_stats() { + let (_dir, gb) = setup_bare_repo(); + let stats = gb + .get_diff_stats(GetDiffStatsRequest { + repository: None, + base: Some(ObjectSelector { + selector: Some(object_selector::Selector::Revision(ObjectName { + revision: "main~3".into(), + })), + }), + head: Some(ObjectSelector { + selector: Some(object_selector::Selector::Revision(ObjectName { + revision: "main".into(), + })), + }), + options: None, + }) + .expect("get_diff_stats"); + assert!( + stats.additions > 0 || stats.changed_files > 0, + "stats should be non-zero: {stats:?}" + ); +} + +#[test] +fn test_compare_commits() { + let (_dir, gb) = setup_bare_repo(); + let result = gb + .compare_commits(gitks::pb::CompareCommitsRequest { + repository: None, + base: Some(ObjectSelector { + selector: Some(object_selector::Selector::Revision(ObjectName { + revision: "feature".into(), + })), + }), + head: Some(ObjectSelector { + selector: Some(object_selector::Selector::Revision(ObjectName { + revision: "main".into(), + })), + }), + straight: false, + first_parent: false, + pagination: Some(Pagination { + page_size: 100, + page_token: String::new(), + }), + }) + .expect("compare_commits"); + + // feature branched off after initial commit; main has 3 more commits + assert!( + !result.commits.is_empty(), + "should find commits between feature and main" + ); + assert!(result.merge_base.is_some(), "should find merge base"); + let stats = result.stats.unwrap(); + assert!(stats.additions > 0, "should have additions: {stats:?}"); +} + +#[test] +fn test_create_commit_with_actions() { + let (_dir, gb) = setup_bare_repo(); + let result = gb + .create_commit(CreateCommitRequest { + repository: None, + branch: "main".into(), + message: "created via API".into(), + author: None, + committer: None, + actions: vec![CreateCommitAction { + action: create_commit_action::Action::CreateCommitActionCreate as i32, + file_path: "api_file.txt".into(), + previous_path: String::new(), + content: b"hello from api".to_vec(), + encoding: String::new(), + executable: false, + last_commit_oid: None, + }], + start_revision: Some(ObjectSelector { + selector: Some(object_selector::Selector::Revision(ObjectName { + revision: "main".into(), + })), + }), + force: false, + trailers: vec![], + }) + .expect("create_commit"); + + let commit = result.commit.unwrap(); + assert_eq!(commit.subject, "created via API"); + assert!(!commit.parent_oids.is_empty(), "should have parent"); + + // Verify the file was created + let blob = gb + .get_blob(GetBlobRequest { + repository: None, + revision: Some(ObjectSelector { + selector: Some(object_selector::Selector::Revision(ObjectName { + revision: "main".into(), + })), + }), + path: "api_file.txt".into(), + oid: None, + max_bytes: 0, + }) + .expect("get_blob after create_commit"); + assert_eq!(blob.data, b"hello from api"); +} + +#[test] +fn test_create_commit_delete_action() { + let (_dir, gb) = setup_bare_repo(); + let result = gb + .create_commit(CreateCommitRequest { + repository: None, + branch: "main".into(), + message: "delete src.txt".into(), + author: None, + committer: None, + actions: vec![CreateCommitAction { + action: create_commit_action::Action::CreateCommitActionDelete as i32, + file_path: "src.txt".into(), + previous_path: String::new(), + content: vec![], + encoding: String::new(), + executable: false, + last_commit_oid: None, + }], + start_revision: Some(ObjectSelector { + selector: Some(object_selector::Selector::Revision(ObjectName { + revision: "main".into(), + })), + }), + force: false, + trailers: vec![], + }) + .expect("create_commit delete"); + + let commit = result.commit.unwrap(); + assert_eq!(commit.subject, "delete src.txt"); + + // Verify file is gone + let blob_result = gb.get_blob(GetBlobRequest { + repository: None, + revision: Some(ObjectSelector { + selector: Some(object_selector::Selector::Revision(ObjectName { + revision: "main".into(), + })), + }), + path: "src.txt".into(), + oid: None, + max_bytes: 0, + }); + assert!(blob_result.is_err(), "src.txt should be deleted"); +} + +#[test] +fn test_list_tree_recursive() { + let (_dir, gb) = setup_bare_repo(); + let result = gb + .list_tree(ListTreeRequest { + repository: None, + revision: Some(ObjectSelector { + selector: Some(object_selector::Selector::Revision(ObjectName { + revision: "main".into(), + })), + }), + path: String::new(), + recursive: true, + pagination: None, + }) + .expect("list_tree recursive"); + + let paths: Vec = result.entries.iter().map(|e| e.path.clone()).collect(); + assert!( + paths.iter().any(|p| p.contains("src/lib/mod.rs")), + "recursive tree should include nested files, got: {paths:?}" + ); +} + +#[test] +fn test_get_tree_subpath() { + let (_dir, gb) = setup_bare_repo(); + let result = gb + .get_tree(GetTreeRequest { + repository: None, + revision: Some(ObjectSelector { + selector: Some(object_selector::Selector::Revision(ObjectName { + revision: "main".into(), + })), + }), + path: "src".into(), + }) + .expect("get_tree subpath"); + + // OID should be the src subtree, not the root tree + assert!(result.oid.is_some()); + let root_tree = gb + .get_tree(GetTreeRequest { + repository: None, + revision: Some(ObjectSelector { + selector: Some(object_selector::Selector::Revision(ObjectName { + revision: "main".into(), + })), + }), + path: String::new(), + }) + .expect("get_tree root"); + assert_ne!( + result.oid.unwrap().hex, + root_tree.oid.unwrap().hex, + "subtree OID should differ from root tree OID" + ); +} + +#[test] +fn test_find_files() { + let (_dir, gb) = setup_bare_repo(); + let result = gb + .find_files(FindFilesRequest { + repository: None, + revision: Some(ObjectSelector { + selector: Some(object_selector::Selector::Revision(ObjectName { + revision: "main".into(), + })), + }), + pattern: "mod.rs".into(), + pathspec: vec![], + pagination: None, + }) + .expect("find_files"); + + assert!(!result.files.is_empty(), "should find mod.rs files"); + assert!( + result.files.iter().all(|f| f.path.contains("mod.rs")), + "all results should match pattern" + ); +} + +#[test] +fn test_list_tags() { + let (_dir, gb) = setup_bare_repo(); + let result = gb + .list_tags(ListTagsRequest { + repository: None, + pattern: String::new(), + pagination: None, + sort_direction: 0, + }) + .expect("list_tags"); + let names: Vec = result.tags.iter().map(|t| t.name.clone()).collect(); + assert!(names.contains(&"v0.1.0".to_string()), "names: {names:?}"); +} + +#[test] +fn test_fsck_clean_repo() { + let (_dir, gb) = setup_bare_repo(); + let result = gb + .fsck(FsckRequest { + repository: None, + strict: false, + connectivity_only: false, + }) + .expect("fsck"); + assert!(result.ok); + assert!(result.errors.is_empty()); +} + +#[test] +fn test_get_patch() { + let (_dir, gb) = setup_bare_repo(); + let patches = gb + .get_patch(GetPatchRequest { + repository: None, + base: Some(ObjectSelector { + selector: Some(object_selector::Selector::Revision(ObjectName { + revision: "main~1".into(), + })), + }), + head: Some(ObjectSelector { + selector: Some(object_selector::Selector::Revision(ObjectName { + revision: "main".into(), + })), + }), + options: None, + }) + .expect("get_patch"); + assert!(!patches.is_empty()); + let combined: String = patches + .iter() + .map(|p| String::from_utf8_lossy(&p.data).to_string()) + .collect(); + assert!( + combined.contains("diff --git") || combined.contains("@@"), + "patch should contain diff output: {combined}" + ); +} + +#[test] +fn test_oid_binary_encoding() { + let (_dir, gb) = setup_bare_repo(); + let commit = gb + .get_commit(GetCommitRequest { + repository: None, + revision: Some(ObjectSelector { + selector: Some(object_selector::Selector::Revision(ObjectName { + revision: "main".into(), + })), + }), + include_stats: false, + include_raw: false, + }) + .expect("get_commit"); + let oid = commit.oid.unwrap(); + // Binary value should be raw bytes, not hex string bytes + assert_eq!( + oid.value.len(), + 20, + "SHA-1 binary is 20 bytes, got {}", + oid.value.len() + ); + assert_eq!(oid.hex.len(), 40, "SHA-1 hex is 40 chars"); + // Verify hex and binary match + let hex_from_bytes: String = oid.value.iter().map(|b| format!("{b:02x}")).collect(); + assert_eq!(hex_from_bytes, oid.hex, "binary and hex must match"); +} diff --git a/tests/merge_test.rs b/tests/merge_test.rs new file mode 100644 index 0000000..6208338 --- /dev/null +++ b/tests/merge_test.rs @@ -0,0 +1,300 @@ +mod common; + +use gitks::pb::*; + +#[test] +fn test_check_merge_no_conflict() { + let (_dir, gb) = common::setup_bare_repo(); + let result = gb + .check_merge(CheckMergeRequest { + repository: None, + target: Some(ObjectSelector { + selector: Some(object_selector::Selector::Revision(ObjectName { + revision: "main".into(), + })), + }), + source: Some(ObjectSelector { + selector: Some(object_selector::Selector::Revision(ObjectName { + revision: "feature".into(), + })), + }), + options: None, + }) + .expect("check_merge"); + + assert!( + result.status == merge_result::Status::MergeResultStatusMerged as i32 + || result.status == merge_result::Status::MergeResultStatusFastForward as i32 + || result.status == merge_result::Status::MergeResultStatusAlreadyUpToDate as i32, + "merge should be clean, got status: {}", + result.status + ); +} + +#[test] +fn test_check_merge_with_conflict() { + let (_dir, gb) = common::setup_bare_repo_with_conflict(); + let result = gb + .check_merge(CheckMergeRequest { + repository: None, + target: Some(ObjectSelector { + selector: Some(object_selector::Selector::Revision(ObjectName { + revision: "branch-a".into(), + })), + }), + source: Some(ObjectSelector { + selector: Some(object_selector::Selector::Revision(ObjectName { + revision: "branch-b".into(), + })), + }), + options: None, + }) + .expect("check_merge with conflict"); + + assert_eq!( + result.status, + merge_result::Status::MergeResultStatusConflicts as i32, + "merge should have conflicts" + ); + assert!(!result.conflicts.is_empty(), "should list conflicted files"); +} + +#[test] +fn test_check_merge_already_up_to_date() { + let (_dir, gb) = common::setup_bare_repo(); + let result = gb + .check_merge(CheckMergeRequest { + repository: None, + target: Some(ObjectSelector { + selector: Some(object_selector::Selector::Revision(ObjectName { + revision: "main".into(), + })), + }), + source: Some(ObjectSelector { + selector: Some(object_selector::Selector::Revision(ObjectName { + revision: "main".into(), + })), + }), + options: None, + }) + .expect("check_merge same ref"); + + assert_eq!( + result.status, + merge_result::Status::MergeResultStatusAlreadyUpToDate as i32 + ); +} + +#[test] +fn test_merge_fast_forward() { + let (_dir, gb) = common::setup_bare_repo(); + let result = gb + .merge(MergeRequest { + repository: None, + target_branch: "feature".into(), + source: Some(ObjectSelector { + selector: Some(object_selector::Selector::Revision(ObjectName { + revision: "main".into(), + })), + }), + committer: None, + message: String::new(), + options: None, + }) + .expect("merge fast-forward"); + + assert!( + result.status == merge_result::Status::MergeResultStatusFastForward as i32 + || result.status == merge_result::Status::MergeResultStatusAlreadyUpToDate as i32, + "feature should fast-forward to main, got: {}", + result.status + ); +} + +#[test] +fn test_merge_with_conflict() { + let (_dir, gb) = common::setup_bare_repo_with_conflict(); + let result = gb + .merge(MergeRequest { + repository: None, + target_branch: "branch-a".into(), + source: Some(ObjectSelector { + selector: Some(object_selector::Selector::Revision(ObjectName { + revision: "branch-b".into(), + })), + }), + committer: None, + message: String::new(), + options: None, + }) + .expect("merge with conflict"); + + assert_eq!( + result.status, + merge_result::Status::MergeResultStatusConflicts as i32, + "should detect conflicts" + ); +} + +#[test] +fn test_merge_fast_forward_only_aborts_non_fast_forward() { + let (_dir, gb) = common::setup_bare_repo_with_conflict(); + let result = gb + .merge(MergeRequest { + repository: None, + target_branch: "branch-a".into(), + source: Some(ObjectSelector { + selector: Some(object_selector::Selector::Revision(ObjectName { + revision: "branch-b".into(), + })), + }), + committer: None, + message: String::new(), + options: Some(MergeOptions { + fast_forward: merge_options::FastForwardMode::MergeFastForwardModeOnly as i32, + ..Default::default() + }), + }) + .expect("merge fast-forward only"); + + assert_eq!( + result.status, + merge_result::Status::MergeResultStatusAborted as i32 + ); + assert!(result.commit.is_none()); +} + +#[test] +fn test_list_merge_conflicts() { + let (_dir, gb) = common::setup_bare_repo_with_conflict(); + let result = gb + .list_merge_conflicts(ListMergeConflictsRequest { + repository: None, + target: Some(ObjectSelector { + selector: Some(object_selector::Selector::Revision(ObjectName { + revision: "branch-a".into(), + })), + }), + source: Some(ObjectSelector { + selector: Some(object_selector::Selector::Revision(ObjectName { + revision: "branch-b".into(), + })), + }), + pagination: None, + }) + .expect("list_merge_conflicts"); + + assert!(!result.conflicts.is_empty(), "should list conflicted files"); + assert!( + result.conflicts.iter().any(|c| c.path == "file.txt"), + "file.txt should be conflicted" + ); +} + +#[test] +fn test_resolve_merge_conflicts() { + let (_dir, gb) = common::setup_bare_repo_with_conflict(); + + let result = gb + .resolve_merge_conflicts(ResolveMergeConflictsRequest { + repository: None, + target_branch: "branch-a".into(), + source: Some(ObjectSelector { + selector: Some(object_selector::Selector::Revision(ObjectName { + revision: "branch-b".into(), + })), + }), + resolutions: vec![ResolveMergeConflict { + path: "file.txt".into(), + content: b"resolved content\n".to_vec(), + }], + committer: None, + message: "resolved conflicts".into(), + }) + .expect("resolve_merge_conflicts"); + + assert_eq!( + result.status, + merge_result::Status::MergeResultStatusMerged as i32 + ); + assert!(result.commit.is_some()); + + let blob = gb + .get_blob(GetBlobRequest { + repository: None, + revision: Some(ObjectSelector { + selector: Some(object_selector::Selector::Revision(ObjectName { + revision: "branch-a".into(), + })), + }), + path: "file.txt".into(), + oid: None, + max_bytes: 0, + }) + .expect("get resolved blob"); + assert_eq!(String::from_utf8_lossy(&blob.data), "resolved content\n"); +} + +#[test] +fn test_rebase() { + let (_dir, gb) = common::setup_bare_repo(); + + gb.create_commit(CreateCommitRequest { + repository: None, + branch: "feature".into(), + message: "feature work".into(), + author: None, + committer: None, + actions: vec![CreateCommitAction { + action: create_commit_action::Action::CreateCommitActionCreate as i32, + file_path: "feature.txt".into(), + previous_path: String::new(), + content: b"feature content".to_vec(), + encoding: String::new(), + executable: false, + last_commit_oid: None, + }], + start_revision: Some(ObjectSelector { + selector: Some(object_selector::Selector::Revision(ObjectName { + revision: "feature".into(), + })), + }), + force: false, + trailers: vec![], + }) + .expect("create feature commit"); + + let result = gb + .rebase(RebaseRequest { + repository: None, + branch: "feature".into(), + upstream: Some(ObjectSelector { + selector: Some(object_selector::Selector::Revision(ObjectName { + revision: "main".into(), + })), + }), + committer: None, + }) + .expect("rebase"); + + assert_eq!( + result.status, + rebase_result::Status::RebaseResultStatusRebased as i32 + ); + assert!(result.head.is_some()); + + let blob = gb + .get_blob(GetBlobRequest { + repository: None, + revision: Some(ObjectSelector { + selector: Some(object_selector::Selector::Revision(ObjectName { + revision: "feature".into(), + })), + }), + path: "feature.txt".into(), + oid: None, + max_bytes: 0, + }) + .expect("get rebased feature file"); + assert_eq!(String::from_utf8_lossy(&blob.data), "feature content"); +} diff --git a/tests/tag_test.rs b/tests/tag_test.rs new file mode 100644 index 0000000..4a650a1 --- /dev/null +++ b/tests/tag_test.rs @@ -0,0 +1,146 @@ +mod common; + +use gitks::pb::*; + +#[test] +fn test_list_tags() { + let (_dir, gb) = common::setup_bare_repo(); + let result = gb + .list_tags(ListTagsRequest { + repository: None, + pattern: String::new(), + pagination: None, + sort_direction: 0, + }) + .expect("list_tags"); + let names: Vec = result.tags.iter().map(|t| t.name.clone()).collect(); + assert!(names.contains(&"v0.1.0".to_string())); +} + +#[test] +fn test_get_tag() { + let (_dir, gb) = common::setup_bare_repo(); + let tag = gb + .get_tag(GetTagRequest { + repository: None, + name: "v0.1.0".into(), + include_raw: false, + }) + .expect("get_tag"); + + assert_eq!(tag.name, "v0.1.0"); + assert!(tag.target_oid.is_some()); + assert_eq!(tag.full_ref, "refs/tags/v0.1.0"); +} + +#[test] +fn test_create_and_delete_lightweight_tag() { + let (_dir, gb) = common::setup_bare_repo(); + let tag = gb + .create_tag(CreateTagRequest { + repository: None, + name: "v0.2.0".into(), + target: Some(ObjectSelector { + selector: Some(object_selector::Selector::Revision(ObjectName { + revision: "main".into(), + })), + }), + message: String::new(), + tagger: None, + force: false, + annotated: false, + }) + .expect("create_tag"); + + assert_eq!(tag.name, "v0.2.0"); + assert!(!tag.annotated); + + gb.delete_tag(DeleteTagRequest { + repository: None, + name: "v0.2.0".into(), + }) + .expect("delete_tag"); + + let result = gb.get_tag(GetTagRequest { + repository: None, + name: "v0.2.0".into(), + include_raw: false, + }); + assert!(result.is_err(), "deleted tag should not exist"); +} + +#[test] +fn test_create_annotated_tag() { + let (_dir, gb) = common::setup_bare_repo(); + let tag = gb + .create_tag(CreateTagRequest { + repository: None, + name: "v1.0.0".into(), + target: Some(ObjectSelector { + selector: Some(object_selector::Selector::Revision(ObjectName { + revision: "main".into(), + })), + }), + message: "Release v1.0.0".into(), + tagger: None, + force: false, + annotated: true, + }) + .expect("create annotated tag"); + + assert_eq!(tag.name, "v1.0.0"); + assert!(tag.annotated, "should be annotated"); + assert!(tag.tag_oid.is_some(), "annotated tag should have tag_oid"); + assert!( + tag.message.contains("Release v1.0.0"), + "message should be set" + ); +} + +#[test] +fn test_list_tags_with_pattern() { + let (_dir, gb) = common::setup_bare_repo(); + + gb.create_tag(CreateTagRequest { + repository: None, + name: "release-1.0".into(), + target: Some(ObjectSelector { + selector: Some(object_selector::Selector::Revision(ObjectName { + revision: "main".into(), + })), + }), + message: String::new(), + tagger: None, + force: false, + annotated: false, + }) + .expect("create release tag"); + + let result = gb + .list_tags(ListTagsRequest { + repository: None, + pattern: "release".into(), + pagination: None, + sort_direction: 0, + }) + .expect("list_tags with pattern"); + + assert!( + result.tags.iter().all(|t| t.name.contains("release")), + "all tags should match pattern" + ); + assert!(!result.tags.is_empty()); +} + +#[test] +fn test_verify_tag() { + let (_dir, gb) = common::setup_bare_repo(); + let result = gb + .verify_tag(VerifyTagRequest { + repository: None, + name: "v0.1.0".into(), + }) + .expect("verify_tag"); + + assert!(!result.verified, "unsigned tag should not be verified"); +} diff --git a/tests/tree_test.rs b/tests/tree_test.rs new file mode 100644 index 0000000..835731f --- /dev/null +++ b/tests/tree_test.rs @@ -0,0 +1,176 @@ +mod common; + +use gitks::pb::*; + +#[test] +fn test_list_tree_recursive() { + let (_dir, gb) = common::setup_bare_repo(); + let result = gb + .list_tree(ListTreeRequest { + repository: None, + revision: Some(ObjectSelector { + selector: Some(object_selector::Selector::Revision(ObjectName { + revision: "main".into(), + })), + }), + path: String::new(), + recursive: true, + pagination: None, + }) + .expect("list_tree recursive"); + + let paths: Vec = result.entries.iter().map(|e| e.path.clone()).collect(); + assert!( + paths.iter().any(|p| p.contains("src/lib/mod.rs")), + "should include nested files, got: {:?}", + paths + ); +} + +#[test] +fn test_get_tree_subpath() { + let (_dir, gb) = common::setup_bare_repo(); + let result = gb + .get_tree(GetTreeRequest { + repository: None, + revision: Some(ObjectSelector { + selector: Some(object_selector::Selector::Revision(ObjectName { + revision: "main".into(), + })), + }), + path: "src".into(), + }) + .expect("get_tree subpath"); + + assert!(result.oid.is_some()); + let root_tree = gb + .get_tree(GetTreeRequest { + repository: None, + revision: Some(ObjectSelector { + selector: Some(object_selector::Selector::Revision(ObjectName { + revision: "main".into(), + })), + }), + path: String::new(), + }) + .expect("get_tree root"); + assert_ne!( + result.oid.unwrap().hex, + root_tree.oid.unwrap().hex, + "subtree OID should differ from root" + ); +} + +#[test] +fn test_find_files() { + let (_dir, gb) = common::setup_bare_repo(); + let result = gb + .find_files(FindFilesRequest { + repository: None, + revision: Some(ObjectSelector { + selector: Some(object_selector::Selector::Revision(ObjectName { + revision: "main".into(), + })), + }), + pattern: "mod.rs".into(), + pathspec: vec![], + pagination: None, + }) + .expect("find_files"); + + assert!(!result.files.is_empty()); + assert!(result.files.iter().all(|f| f.path.contains("mod.rs"))); +} + +#[test] +fn test_get_blob() { + let (_dir, gb) = common::setup_bare_repo(); + let blob = gb + .get_blob(GetBlobRequest { + repository: None, + revision: Some(ObjectSelector { + selector: Some(object_selector::Selector::Revision(ObjectName { + revision: "main".into(), + })), + }), + path: "README.md".into(), + oid: None, + max_bytes: 0, + }) + .expect("get_blob"); + + let content = String::from_utf8_lossy(&blob.data); + assert!(content.contains("# Test")); + assert!(blob.size > 0); + assert!(!blob.binary); +} + +#[test] +fn test_get_blob_with_truncation() { + let (_dir, gb) = common::setup_bare_repo(); + let blob = gb + .get_blob(GetBlobRequest { + repository: None, + revision: Some(ObjectSelector { + selector: Some(object_selector::Selector::Revision(ObjectName { + revision: "main".into(), + })), + }), + path: "README.md".into(), + oid: None, + max_bytes: 5, + }) + .expect("get_blob truncated"); + + assert_eq!(blob.data.len(), 5); + assert!(blob.truncated); + assert!( + blob.size > 5, + "size should be original size, not truncated size" + ); +} + +#[test] +fn test_get_file_metadata() { + let (_dir, gb) = common::setup_bare_repo(); + let meta = gb + .get_file_metadata(GetFileMetadataRequest { + repository: None, + revision: Some(ObjectSelector { + selector: Some(object_selector::Selector::Revision(ObjectName { + revision: "main".into(), + })), + }), + path: "README.md".into(), + }) + .expect("get_file_metadata"); + + assert_eq!(meta.path, "README.md"); + assert!(meta.oid.is_some()); + assert_eq!(meta.r#type, ObjectType::Blob as i32); +} + +#[test] +fn test_list_tree_with_pagination() { + let (_dir, gb) = common::setup_bare_repo(); + let result = gb + .list_tree(ListTreeRequest { + repository: None, + revision: Some(ObjectSelector { + selector: Some(object_selector::Selector::Revision(ObjectName { + revision: "main".into(), + })), + }), + path: String::new(), + recursive: false, + pagination: Some(Pagination { + page_size: 1, + page_token: String::new(), + }), + }) + .expect("list_tree paginated"); + + assert_eq!(result.entries.len(), 1); + let pi = result.page_info.unwrap(); + assert!(pi.has_next_page); +} diff --git a/tree/find_files.rs b/tree/find_files.rs new file mode 100644 index 0000000..c82f354 --- /dev/null +++ b/tree/find_files.rs @@ -0,0 +1,56 @@ +use crate::bare::GitBare; +use crate::error::GitResult; +use crate::paginate; +use crate::pb::{ + FileMetadata, FindFilesRequest, FindFilesResponse, ListTreeRequest, ObjectType, tree_entry, +}; + +impl GitBare { + pub fn find_files(&self, request: FindFilesRequest) -> GitResult { + let revision = request.revision.clone(); + let root = if request.pathspec.is_empty() { + vec![String::new()] + } else { + request.pathspec.clone() + }; + + let mut files = Vec::new(); + for pathspec in root { + let response = self.list_tree(ListTreeRequest { + repository: request.repository.clone(), + revision: revision.clone(), + path: pathspec, + recursive: true, + pagination: None, + })?; + for entry in response.entries { + if !request.pattern.is_empty() && !entry.path.contains(&request.pattern) { + continue; + } + let object_type = match tree_entry::EntryType::try_from(entry.r#type) + .unwrap_or(tree_entry::EntryType::TreeEntryTypeUnspecified) + { + tree_entry::EntryType::TreeEntryTypeTree => ObjectType::Tree, + tree_entry::EntryType::TreeEntryTypeCommit => ObjectType::Commit, + tree_entry::EntryType::TreeEntryTypeUnspecified => ObjectType::Unspecified, + _ => ObjectType::Blob, + } as i32; + files.push(FileMetadata { + path: entry.path, + oid: entry.oid, + mode: entry.mode, + size: entry.size, + r#type: object_type, + binary: false, + }); + } + } + + files.sort_by(|a, b| a.path.cmp(&b.path)); + let (files, page_info) = paginate::paginate(&files, request.pagination.as_ref()); + Ok(FindFilesResponse { + files, + page_info: Some(page_info), + }) + } +} diff --git a/tree/get_file_metadata.rs b/tree/get_file_metadata.rs new file mode 100644 index 0000000..abd74dc --- /dev/null +++ b/tree/get_file_metadata.rs @@ -0,0 +1,38 @@ +use gix::object::tree::EntryKind; + +use crate::bare::GitBare; +use crate::error::{GitError, GitResult}; +use crate::pb::{FileMetadata, GetFileMetadataRequest, ObjectType, object_selector}; + +impl GitBare { + pub fn get_file_metadata(&self, request: GetFileMetadataRequest) -> GitResult { + let repo = self.gix_repo()?; + let revision = match request.revision.and_then(|s| s.selector) { + Some(object_selector::Selector::Oid(oid)) => oid.hex, + Some(object_selector::Selector::Revision(name)) => name.revision, + None => "HEAD".into(), + }; + let tree = repo + .rev_parse_single(format!("{}^{{tree}}", revision).as_str())? + .object()? + .try_into_tree() + .map_err(|e| GitError::Gix(e.to_string()))?; + let entry = tree + .lookup_entry_by_path(&request.path)? + .ok_or_else(|| GitError::NotFound(request.path.clone()))?; + let hex = entry.id().to_string(); + let kind = match entry.mode().kind() { + EntryKind::Tree => ObjectType::Tree, + EntryKind::Commit => ObjectType::Commit, + _ => ObjectType::Blob, + } as i32; + Ok(FileMetadata { + path: request.path, + oid: Some(self.oid_to_pb(hex)), + mode: u32::from_str_radix(&format!("{:o}", entry.mode()), 8).unwrap_or(0), + size: 0, + r#type: kind, + binary: false, + }) + } +} diff --git a/tree/get_tree.rs b/tree/get_tree.rs new file mode 100644 index 0000000..30e9c24 --- /dev/null +++ b/tree/get_tree.rs @@ -0,0 +1,43 @@ +use crate::bare::GitBare; +use crate::error::{GitError, GitResult}; +use crate::pb::{GetTreeRequest, ListTreeRequest, Tree}; + +impl GitBare { + pub fn get_tree(&self, request: GetTreeRequest) -> GitResult { + let entries = self.list_tree(ListTreeRequest { + repository: request.repository, + revision: request.revision.clone(), + path: request.path.clone(), + recursive: false, + pagination: None, + })?; + let repo = self.gix_repo()?; + let revision = request + .revision + .and_then(|s| s.selector) + .map(|s| match s { + crate::pb::object_selector::Selector::Oid(oid) => oid.hex, + crate::pb::object_selector::Selector::Revision(name) => name.revision, + }) + .unwrap_or_else(|| "HEAD".into()); + let root = repo + .rev_parse_single(format!("{}^{{tree}}", revision).as_str())? + .object()? + .try_into_tree() + .map_err(|e| GitError::Gix(e.to_string()))?; + let tree_hex = if request.path.is_empty() { + root.id.to_string() + } else { + root.lookup_entry_by_path(&request.path)? + .ok_or_else(|| GitError::NotFound(request.path.clone()))? + .id() + .to_string() + }; + Ok(Tree { + oid: Some(self.oid_to_pb(tree_hex)), + path: request.path, + entries: entries.entries, + truncated: false, + }) + } +} diff --git a/tree/list_tree.rs b/tree/list_tree.rs new file mode 100644 index 0000000..46b449d --- /dev/null +++ b/tree/list_tree.rs @@ -0,0 +1,87 @@ +use gix::object::tree::EntryKind; + +use crate::bare::GitBare; +use crate::error::{GitError, GitResult}; +use crate::paginate; +use crate::pb::{ListTreeRequest, ListTreeResponse, TreeEntry, object_selector, tree_entry}; + +impl GitBare { + pub fn list_tree(&self, request: ListTreeRequest) -> GitResult { + let repo = self.gix_repo()?; + let revision = match request.revision.clone().and_then(|s| s.selector) { + Some(object_selector::Selector::Oid(oid)) => oid.hex, + Some(object_selector::Selector::Revision(name)) => name.revision, + None => "HEAD".into(), + }; + let mut tree = repo + .rev_parse_single(format!("{}^{{tree}}", revision).as_str())? + .object()? + .try_into_tree() + .map_err(|e| GitError::Gix(e.to_string()))?; + if !request.path.is_empty() { + let entry = tree + .lookup_entry_by_path(&request.path)? + .ok_or_else(|| GitError::NotFound(request.path.clone()))?; + tree = entry + .object()? + .try_into_tree() + .map_err(|e| GitError::Gix(e.to_string()))?; + } + + let base = request.path.trim_matches('/').to_string(); + let mut entries = Vec::new(); + for entry in tree.iter() { + let entry = entry?; + let name = String::from_utf8_lossy(entry.filename()).into_owned(); + let path = if base.is_empty() { + name.clone() + } else { + format!("{base}/{name}") + }; + let kind = entry.kind(); + let hex = entry.id().to_string(); + entries.push(TreeEntry { + name, + path: path.clone(), + oid: Some(self.oid_to_pb(hex)), + r#type: entry_type(kind) as i32, + mode: u32::from_str_radix(&format!("{:o}", entry.mode()), 8).unwrap_or(0), + size: entry_size(&repo, entry.id().to_string().as_str()).unwrap_or(0), + }); + + if request.recursive && matches!(kind, EntryKind::Tree) { + let child = self.list_tree(ListTreeRequest { + repository: request.repository.clone(), + revision: request.revision.clone(), + path, + recursive: true, + pagination: None, + })?; + entries.extend(child.entries); + } + } + + let (entries, page_info) = paginate::paginate(&entries, request.pagination.as_ref()); + Ok(ListTreeResponse { + entries, + page_info: Some(page_info), + truncated: false, + }) + } +} + +fn entry_type(kind: EntryKind) -> tree_entry::EntryType { + match kind { + EntryKind::Tree => tree_entry::EntryType::TreeEntryTypeTree, + EntryKind::Blob => tree_entry::EntryType::TreeEntryTypeBlob, + EntryKind::BlobExecutable => tree_entry::EntryType::TreeEntryTypeExecutable, + EntryKind::Link => tree_entry::EntryType::TreeEntryTypeSymlink, + EntryKind::Commit => tree_entry::EntryType::TreeEntryTypeCommit, + } +} + +fn entry_size(repo: &gix::Repository, oid: &str) -> Option { + let id = gix::hash::ObjectId::from_hex(oid.as_bytes()).ok()?; + let object = repo.find_object(id).ok()?; + object.data.len().try_into().ok() +} diff --git a/tree/mod.rs b/tree/mod.rs new file mode 100644 index 0000000..7dbe0de --- /dev/null +++ b/tree/mod.rs @@ -0,0 +1,4 @@ +pub mod find_files; +pub mod get_file_metadata; +pub mod get_tree; +pub mod list_tree;