feat: add replay feature for recovery

- fix bug commit not update index

Signed-off-by: Pakin <pakin.t@forth.co.th>
This commit is contained in:
Pakin 2026-05-19 12:10:31 +07:00
parent a69ef7b927
commit 2466472235
4 changed files with 693 additions and 140 deletions

460
Cargo.lock generated
View file

@ -16,7 +16,7 @@ checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"cipher", "cipher",
"cpufeatures", "cpufeatures 0.2.17",
] ]
[[package]] [[package]]
@ -400,6 +400,15 @@ dependencies = [
"generic-array", "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]] [[package]]
name = "brotli" name = "brotli"
version = "8.0.2" version = "8.0.2"
@ -439,6 +448,12 @@ version = "1.25.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec"
[[package]]
name = "byteorder"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]] [[package]]
name = "byteorder-lite" name = "byteorder-lite"
version = "0.1.0" version = "0.1.0"
@ -478,6 +493,17 @@ version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
name = "chacha20"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601"
dependencies = [
"cfg-if",
"cpufeatures 0.3.0",
"rand_core 0.10.1",
]
[[package]] [[package]]
name = "chrono" name = "chrono"
version = "0.4.44" version = "0.4.44"
@ -497,10 +523,16 @@ version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
dependencies = [ dependencies = [
"crypto-common", "crypto-common 0.1.7",
"inout", "inout",
] ]
[[package]]
name = "cmov"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f88a43d011fc4a6876cb7344703e297c71dda42494fee094d5f7c76bf13f746"
[[package]] [[package]]
name = "color_quant" name = "color_quant"
version = "1.1.0" version = "1.1.0"
@ -552,6 +584,12 @@ dependencies = [
"crossbeam-utils", "crossbeam-utils",
] ]
[[package]]
name = "const-oid"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c"
[[package]] [[package]]
name = "constant_time_eq" name = "constant_time_eq"
version = "0.3.1" version = "0.3.1"
@ -593,6 +631,15 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "cpufeatures"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201"
dependencies = [
"libc",
]
[[package]] [[package]]
name = "crc" name = "crc"
version = "3.4.0" version = "3.4.0"
@ -658,6 +705,24 @@ dependencies = [
"typenum", "typenum",
] ]
[[package]]
name = "crypto-common"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77727bb15fa921304124b128af125e7e3b968275d1b108b379190264f4423710"
dependencies = [
"hybrid-array",
]
[[package]]
name = "ctutils"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d5515a3834141de9eafb9717ad39eea8247b5674e6066c404e8c4b365d2a29e"
dependencies = [
"cmov",
]
[[package]] [[package]]
name = "deflate64" name = "deflate64"
version = "0.1.12" version = "0.1.12"
@ -690,11 +755,23 @@ version = "0.10.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
dependencies = [ dependencies = [
"block-buffer", "block-buffer 0.10.4",
"crypto-common", "crypto-common 0.1.7",
"subtle", "subtle",
] ]
[[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",
"const-oid",
"crypto-common 0.2.1",
"ctutils",
]
[[package]] [[package]]
name = "displaydoc" name = "displaydoc"
version = "0.2.5" version = "0.2.5"
@ -816,6 +893,12 @@ dependencies = [
"zune-inflate", "zune-inflate",
] ]
[[package]]
name = "fallible-iterator"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7"
[[package]] [[package]]
name = "fastrand" name = "fastrand"
version = "2.4.1" version = "2.4.1"
@ -914,6 +997,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d"
dependencies = [ dependencies = [
"futures-core", "futures-core",
"futures-sink",
] ]
[[package]] [[package]]
@ -977,7 +1061,7 @@ checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"libc", "libc",
"wasi", "wasi 0.11.1+wasi-snapshot-preview1",
] ]
[[package]] [[package]]
@ -1001,6 +1085,7 @@ dependencies = [
"cfg-if", "cfg-if",
"libc", "libc",
"r-efi 6.0.0", "r-efi 6.0.0",
"rand_core 0.10.1",
"wasip2", "wasip2",
"wasip3", "wasip3",
] ]
@ -1032,9 +1117,9 @@ dependencies = [
[[package]] [[package]]
name = "h2" name = "h2"
version = "0.4.13" version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54" checksum = "171fefbc92fe4a4de27e0698d6a5b392d6a0e333506bc49133760b3bcf948733"
dependencies = [ dependencies = [
"atomic-waker", "atomic-waker",
"bytes", "bytes",
@ -1087,7 +1172,16 @@ version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e"
dependencies = [ dependencies = [
"digest", "digest 0.10.7",
]
[[package]]
name = "hmac"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6303bc9732ae41b04cb554b844a762b4115a61bfaa81e3e83050991eeb56863f"
dependencies = [
"digest 0.11.3",
] ]
[[package]] [[package]]
@ -1135,6 +1229,15 @@ version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" 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]] [[package]]
name = "hyper" name = "hyper"
version = "1.9.0" version = "1.9.0"
@ -1437,16 +1540,6 @@ version = "2.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2"
[[package]]
name = "iri-string"
version = "0.7.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "25e659a4bb38e810ebc252e53b5814ff908a8c58c2a9ce2fae1bbec24cbf4e20"
dependencies = [
"memchr",
"serde",
]
[[package]] [[package]]
name = "is_terminal_polyfill" name = "is_terminal_polyfill"
version = "1.70.2" version = "1.70.2"
@ -1504,9 +1597,9 @@ dependencies = [
[[package]] [[package]]
name = "js-sys" name = "js-sys"
version = "0.3.97" version = "0.3.98"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1840c94c045fbcf8ba2812c95db44499f7c64910a912551aaaa541decebcacf" checksum = "67df7112613f8bfd9150013a0314e196f4800d3201ae742489d999db2f979f08"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"futures-util", "futures-util",
@ -1593,7 +1686,7 @@ dependencies = [
"bitflags", "bitflags",
"libc", "libc",
"plain", "plain",
"redox_syscall 0.7.4", "redox_syscall 0.7.5",
] ]
[[package]] [[package]]
@ -1621,7 +1714,7 @@ dependencies = [
"indexmap", "indexmap",
"log", "log",
"quick-xml", "quick-xml",
"rand", "rand 0.9.4",
"rayon", "rayon",
"serde", "serde",
"serde_json", "serde_json",
@ -1685,7 +1778,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c60a23ffb90d527e23192f1246b14746e2f7f071cb84476dd879071696c18a4a" checksum = "c60a23ffb90d527e23192f1246b14746e2f7f071cb84476dd879071696c18a4a"
dependencies = [ dependencies = [
"crc", "crc",
"sha2", "sha2 0.10.9",
] ]
[[package]] [[package]]
@ -1704,6 +1797,16 @@ dependencies = [
"rayon", "rayon",
] ]
[[package]]
name = "md-5"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69b6441f590336821bb897fb28fc622898ccceb1d6cea3fde5ea86b090c4de98"
dependencies = [
"cfg-if",
"digest 0.11.3",
]
[[package]] [[package]]
name = "memchr" name = "memchr"
version = "2.8.0" version = "2.8.0"
@ -1733,7 +1836,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1" checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1"
dependencies = [ dependencies = [
"libc", "libc",
"wasi", "wasi 0.11.1+wasi-snapshot-preview1",
"windows-sys 0.61.2", "windows-sys 0.61.2",
] ]
@ -1795,9 +1898,9 @@ checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086"
[[package]] [[package]]
name = "no_std_io2" name = "no_std_io2"
version = "0.9.3" version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b51ed7824b6e07d354605f4abb3d9d300350701299da96642ee084f5ce631550" checksum = "418abd1b6d34fbf6cae440dc874771b0525a604428704c76e48b29a5e67b8003"
dependencies = [ dependencies = [
"memchr", "memchr",
] ]
@ -1873,6 +1976,24 @@ dependencies = [
"autocfg", "autocfg",
] ]
[[package]]
name = "objc2-core-foundation"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536"
dependencies = [
"bitflags",
]
[[package]]
name = "objc2-system-configuration"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7216bd11cbda54ccabcab84d523dc93b858ec75ecfb3a7d89513fa22464da396"
dependencies = [
"objc2-core-foundation",
]
[[package]] [[package]]
name = "once_cell" name = "once_cell"
version = "1.21.4" version = "1.21.4"
@ -1981,8 +2102,8 @@ version = "0.12.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2"
dependencies = [ dependencies = [
"digest", "digest 0.10.7",
"hmac", "hmac 0.12.1",
] ]
[[package]] [[package]]
@ -2003,19 +2124,38 @@ dependencies = [
] ]
[[package]] [[package]]
name = "pin-project" name = "phf"
version = "1.1.11" version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1749c7ed4bcaf4c3d0a3efc28538844fb29bcdd7d2b67b2be7e20ba861ff517" checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf"
dependencies = [
"phf_shared",
"serde",
]
[[package]]
name = "phf_shared"
version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e57fef6bc5981e38c2ce2d63bfa546861309f875b8a75f092d1d54ae2d64f266"
dependencies = [
"siphasher",
]
[[package]]
name = "pin-project"
version = "1.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cbf0d9e68100b3a7989b4901972f265cd542e560a3a8a724e1e20322f4d06ce9"
dependencies = [ dependencies = [
"pin-project-internal", "pin-project-internal",
] ]
[[package]] [[package]]
name = "pin-project-internal" name = "pin-project-internal"
version = "1.1.11" version = "1.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9b20ed30f105399776b9c883e68e536ef602a16ae6f596d2c473591d6ad64c6" checksum = "a990e22f43e84855daf260dded30524ef4a9021cc7541c26540500a50b624389"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -2068,6 +2208,35 @@ dependencies = [
"portable-atomic", "portable-atomic",
] ]
[[package]]
name = "postgres-protocol"
version = "0.6.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56201207dac53e2f38e848e31b4b91616a6bb6e0c7205b77718994a7f49e70fc"
dependencies = [
"base64",
"byteorder",
"bytes",
"fallible-iterator",
"hmac 0.13.0",
"md-5",
"memchr",
"rand 0.10.1",
"sha2 0.11.0",
"stringprep",
]
[[package]]
name = "postgres-types"
version = "0.2.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8dc729a129e682e8d24170cd30ae1aa01b336b096cbb56df6d534ffec133d186"
dependencies = [
"bytes",
"fallible-iterator",
"postgres-protocol",
]
[[package]] [[package]]
name = "potential_utf" name = "potential_utf"
version = "0.1.5" version = "0.1.5"
@ -2119,18 +2288,18 @@ dependencies = [
[[package]] [[package]]
name = "profiling" name = "profiling"
version = "1.0.17" version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3eb8486b569e12e2c32ad3e204dbaba5e4b5b216e9367044f25f1dba42341773" checksum = "3d595e54a326bc53c1c197b32d295e14b169e3cfeaa8dc82b529f947fba6bcf5"
dependencies = [ dependencies = [
"profiling-procmacros", "profiling-procmacros",
] ]
[[package]] [[package]]
name = "profiling-procmacros" name = "profiling-procmacros"
version = "1.0.17" version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52717f9a02b6965224f95ca2a81e2e0c5c43baacd28ca057577988930b6c3d5b" checksum = "4488a4a36b9a4ba6b9334a32a39971f77c1436ec82c38707bce707699cc3bbcb"
dependencies = [ dependencies = [
"quote", "quote",
"syn", "syn",
@ -2232,9 +2401,9 @@ checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3"
[[package]] [[package]]
name = "quick-xml" name = "quick-xml"
version = "0.39.2" version = "0.39.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "958f21e8e7ceb5a1aa7fa87fab28e7c75976e0bfe7e23ff069e0a260f894067d" checksum = "721da970c312655cde9b4ffe0547f20a8494866a4af5ff51f18b7c633d0c870b"
dependencies = [ dependencies = [
"memchr", "memchr",
] ]
@ -2267,7 +2436,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea"
dependencies = [ dependencies = [
"rand_chacha", "rand_chacha",
"rand_core", "rand_core 0.9.5",
]
[[package]]
name = "rand"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2e8e8bcc7961af1fdac401278c6a831614941f6164ee3bf4ce61b7edb162207"
dependencies = [
"chacha20",
"getrandom 0.4.2",
"rand_core 0.10.1",
] ]
[[package]] [[package]]
@ -2277,7 +2457,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
dependencies = [ dependencies = [
"ppv-lite86", "ppv-lite86",
"rand_core", "rand_core 0.9.5",
] ]
[[package]] [[package]]
@ -2289,6 +2469,12 @@ dependencies = [
"getrandom 0.3.4", "getrandom 0.3.4",
] ]
[[package]]
name = "rand_core"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69"
[[package]] [[package]]
name = "rav1e" name = "rav1e"
version = "0.8.1" version = "0.8.1"
@ -2316,7 +2502,7 @@ dependencies = [
"num-traits", "num-traits",
"paste", "paste",
"profiling", "profiling",
"rand", "rand 0.9.4",
"rand_chacha", "rand_chacha",
"simd_helpers", "simd_helpers",
"thiserror", "thiserror",
@ -2395,9 +2581,9 @@ dependencies = [
[[package]] [[package]]
name = "redox_syscall" name = "redox_syscall"
version = "0.7.4" version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f450ad9c3b1da563fb6948a8e0fb0fb9269711c9c73d9ea1de5058c79c8d643a" checksum = "4666a1a60d8412eab19d94f6d13dcc9cea0a5ef4fdf6a5db306537413c661b1b"
dependencies = [ dependencies = [
"bitflags", "bitflags",
] ]
@ -2676,8 +2862,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"cpufeatures", "cpufeatures 0.2.17",
"digest", "digest 0.10.7",
] ]
[[package]] [[package]]
@ -2693,8 +2879,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"cpufeatures", "cpufeatures 0.2.17",
"digest", "digest 0.10.7",
]
[[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]] [[package]]
@ -2728,6 +2925,12 @@ dependencies = [
"quote", "quote",
] ]
[[package]]
name = "siphasher"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ee5873ec9cce0195efcb7a4e9507a04cd49aec9c83d0389df45b1ef7ba2e649"
[[package]] [[package]]
name = "slab" name = "slab"
version = "0.4.12" version = "0.4.12"
@ -2762,6 +2965,17 @@ version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
[[package]]
name = "stringprep"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1"
dependencies = [
"unicode-bidi",
"unicode-normalization",
"unicode-properties",
]
[[package]] [[package]]
name = "subtle" name = "subtle"
version = "2.6.1" version = "2.6.1"
@ -2857,6 +3071,7 @@ dependencies = [
"serde", "serde",
"serde_json", "serde_json",
"tokio", "tokio",
"tokio-postgres",
"tokio-util", "tokio-util",
"tonic", "tonic",
"tonic-prost", "tonic-prost",
@ -2941,10 +3156,25 @@ dependencies = [
] ]
[[package]] [[package]]
name = "tokio" name = "tinyvec"
version = "1.52.1" version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b67dee974fe86fd92cc45b7a95fdd2f99a36a6d7b0d431a231178d3d670bbcc6" 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.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "110a78583f19d5cdb2c5ccf321d1290344e71313c6c37d43520d386027d18386"
dependencies = [ dependencies = [
"bytes", "bytes",
"libc", "libc",
@ -2978,6 +3208,32 @@ dependencies = [
"tokio", "tokio",
] ]
[[package]]
name = "tokio-postgres"
version = "0.7.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4dd8df5ef180f6364759a6f00f7aadda4fbbac86cdee37480826a6ff9f3574ce"
dependencies = [
"async-trait",
"byteorder",
"bytes",
"fallible-iterator",
"futures-channel",
"futures-util",
"log",
"parking_lot",
"percent-encoding",
"phf",
"pin-project-lite",
"postgres-protocol",
"postgres-types",
"rand 0.10.1",
"socket2",
"tokio",
"tokio-util",
"whoami",
]
[[package]] [[package]]
name = "tokio-rustls" name = "tokio-rustls"
version = "0.26.4" version = "0.26.4"
@ -3014,9 +3270,9 @@ dependencies = [
[[package]] [[package]]
name = "tonic" name = "tonic"
version = "0.14.5" version = "0.14.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fec7c61a0695dc1887c1b53952990f3ad2e3a31453e1f49f10e75424943a93ec" checksum = "ac2a5518c70fa84342385732db33fb3f44bc4cc748936eb5833d2df34d6445ef"
dependencies = [ dependencies = [
"async-trait", "async-trait",
"axum", "axum",
@ -3043,9 +3299,9 @@ dependencies = [
[[package]] [[package]]
name = "tonic-build" name = "tonic-build"
version = "0.14.5" version = "0.14.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1882ac3bf5ef12877d7ed57aad87e75154c11931c2ba7e6cde5e22d63522c734" checksum = "c68f61875ac5293cf72e6c8cf0158086428c82c37229e98c840878f1706b0322"
dependencies = [ dependencies = [
"prettyplease", "prettyplease",
"proc-macro2", "proc-macro2",
@ -3055,9 +3311,9 @@ dependencies = [
[[package]] [[package]]
name = "tonic-prost" name = "tonic-prost"
version = "0.14.5" version = "0.14.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a55376a0bbaa4975a3f10d009ad763d8f4108f067c7c2e74f3001fb49778d309" checksum = "50849f68853be452acf590cde0b146665b8d507b3b8af17261df47e02c209ea0"
dependencies = [ dependencies = [
"bytes", "bytes",
"prost", "prost",
@ -3066,9 +3322,9 @@ dependencies = [
[[package]] [[package]]
name = "tonic-prost-build" name = "tonic-prost-build"
version = "0.14.5" version = "0.14.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3144df636917574672e93d0f56d7edec49f90305749c668df5101751bb8f95a" checksum = "654e5643eff75d7f8c99197ce1440ed19a3474eada74c12bbac488b2cafdae27"
dependencies = [ dependencies = [
"prettyplease", "prettyplease",
"proc-macro2", "proc-macro2",
@ -3101,20 +3357,20 @@ dependencies = [
[[package]] [[package]]
name = "tower-http" name = "tower-http"
version = "0.6.8" version = "0.6.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" checksum = "68d6fdd9f81c2819c9a8b0e0cd91660e7746a8e6ea2ba7c6b2b057985f6bcb51"
dependencies = [ dependencies = [
"bitflags", "bitflags",
"bytes", "bytes",
"futures-util", "futures-util",
"http", "http",
"http-body", "http-body",
"iri-string",
"pin-project-lite", "pin-project-lite",
"tower", "tower",
"tower-layer", "tower-layer",
"tower-service", "tower-service",
"url",
] ]
[[package]] [[package]]
@ -3179,12 +3435,33 @@ version = "2.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142" checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142"
[[package]]
name = "unicode-bidi"
version = "0.3.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5"
[[package]] [[package]]
name = "unicode-ident" name = "unicode-ident"
version = "1.0.24" version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" 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-properties"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7df058c713841ad818f1dc5d3fd88063241cc61f49f5fbea4b951e8cf5a8d71d"
[[package]] [[package]]
name = "unicode-xid" name = "unicode-xid"
version = "0.2.6" version = "0.2.6"
@ -3280,6 +3557,15 @@ version = "0.11.1+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
[[package]]
name = "wasi"
version = "0.14.7+wasi-0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "883478de20367e224c0090af9cf5f9fa85bed63a95c1abf3afc5c083ebc06e8c"
dependencies = [
"wasip2",
]
[[package]] [[package]]
name = "wasip2" name = "wasip2"
version = "1.0.3+wasi-0.2.9" version = "1.0.3+wasi-0.2.9"
@ -3299,10 +3585,19 @@ dependencies = [
] ]
[[package]] [[package]]
name = "wasm-bindgen" name = "wasite"
version = "0.2.120" version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df52b6d9b87e0c74c9edfa1eb2d9bf85e5d63515474513aa50fa181b3c4f5db1" checksum = "66fe902b4a6b8028a753d5424909b764ccf79b7a209eac9bf97e59cda9f71a42"
dependencies = [
"wasi 0.14.7+wasi-0.2.4",
]
[[package]]
name = "wasm-bindgen"
version = "0.2.121"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49ace1d07c165b0864824eee619580c4689389afa9dc9ed3a4c75040d82e6790"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"once_cell", "once_cell",
@ -3313,9 +3608,9 @@ dependencies = [
[[package]] [[package]]
name = "wasm-bindgen-futures" name = "wasm-bindgen-futures"
version = "0.4.70" version = "0.4.71"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af934872acec734c2d80e6617bbb5ff4f12b052dd8e6332b0817bce889516084" checksum = "96492d0d3ffba25305a7dc88720d250b1401d7edca02cc3bcd50633b424673b8"
dependencies = [ dependencies = [
"js-sys", "js-sys",
"wasm-bindgen", "wasm-bindgen",
@ -3323,9 +3618,9 @@ dependencies = [
[[package]] [[package]]
name = "wasm-bindgen-macro" name = "wasm-bindgen-macro"
version = "0.2.120" version = "0.2.121"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78b1041f495fb322e64aca85f5756b2172e35cd459376e67f2a6c9dffcedb103" checksum = "8e68e6f4afd367a562002c05637acb8578ff2dea1943df76afb9e83d177c8578"
dependencies = [ dependencies = [
"quote", "quote",
"wasm-bindgen-macro-support", "wasm-bindgen-macro-support",
@ -3333,9 +3628,9 @@ dependencies = [
[[package]] [[package]]
name = "wasm-bindgen-macro-support" name = "wasm-bindgen-macro-support"
version = "0.2.120" version = "0.2.121"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9dcd0ff20416988a18ac686d4d4d0f6aae9ebf08a389ff5d29012b05af2a1b41" checksum = "d95a9ec35c64b2a7cb35d3fead40c4238d0940c86d107136999567a4703259f2"
dependencies = [ dependencies = [
"bumpalo", "bumpalo",
"proc-macro2", "proc-macro2",
@ -3346,9 +3641,9 @@ dependencies = [
[[package]] [[package]]
name = "wasm-bindgen-shared" name = "wasm-bindgen-shared"
version = "0.2.120" version = "0.2.121"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49757b3c82ebf16c57d69365a142940b384176c24df52a087fb748e2085359ea" checksum = "c4e0100b01e9f0d03189a92b96772a1fb998639d981193d7dbab487302513441"
dependencies = [ dependencies = [
"unicode-ident", "unicode-ident",
] ]
@ -3389,9 +3684,9 @@ dependencies = [
[[package]] [[package]]
name = "web-sys" name = "web-sys"
version = "0.3.97" version = "0.3.98"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2eadbac71025cd7b0834f20d1fe8472e8495821b4e9801eb0a60bd1f19827602" checksum = "4b572dff8bcf38bad0fa19729c89bb5748b2b9b1d8be70cf90df697e3a8f32aa"
dependencies = [ dependencies = [
"js-sys", "js-sys",
"wasm-bindgen", "wasm-bindgen",
@ -3403,6 +3698,19 @@ version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88" checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88"
[[package]]
name = "whoami"
version = "2.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "998767ef88740d1f5b0682a9c53c24431453923962269c2db68ee43788c5a40d"
dependencies = [
"libc",
"libredox",
"objc2-system-configuration",
"wasite",
"web-sys",
]
[[package]] [[package]]
name = "winapi-util" name = "winapi-util"
version = "0.1.11" version = "0.1.11"
@ -3817,7 +4125,7 @@ dependencies = [
"deflate64", "deflate64",
"flate2", "flate2",
"getrandom 0.3.4", "getrandom 0.3.4",
"hmac", "hmac 0.12.1",
"indexmap", "indexmap",
"lzma-rust2", "lzma-rust2",
"memchr", "memchr",

View file

@ -30,6 +30,7 @@ tokio-util = { version = "0.7.18", features = ["io"] }
tonic = { version = "0.14.2", features = ["transport"] } tonic = { version = "0.14.2", features = ["transport"] }
tonic-prost = "0.14.2" tonic-prost = "0.14.2"
uuid = { version = "1.20.0", features = ["v4"] } uuid = { version = "1.20.0", features = ["v4"] }
tokio-postgres = "0.7.17"
[build-dependencies] [build-dependencies]
tonic-prost-build = "0.14.2" tonic-prost-build = "0.14.2"

View file

@ -1,10 +1,16 @@
use std::{collections::HashMap, io::Cursor, sync::Arc, time::Duration}; use std::{
collections::HashMap,
io::Cursor,
path::{Path, PathBuf},
sync::Arc,
time::Duration,
};
use async_compression::tokio::bufread::BrotliEncoder; use async_compression::tokio::bufread::BrotliEncoder;
use axum::{ use axum::{
Json, Router, Json, Router,
body::Body, body::Body,
extract::{Query, State}, extract::{DefaultBodyLimit, Query, State},
response::{IntoResponse, Response}, response::{IntoResponse, Response},
routing::{get, post}, routing::{get, post},
}; };
@ -22,7 +28,7 @@ use image::load_from_memory;
use log::{error, info, warn}; use log::{error, info, warn};
use redis::{AsyncTypedCommands, Connection, TypedCommands, aio::MultiplexedConnection}; use redis::{AsyncTypedCommands, Connection, TypedCommands, aio::MultiplexedConnection};
use reqwest::header; use reqwest::header;
use serde::Deserialize; use serde::{Deserialize, Serialize};
use serde_json::{Value, json}; use serde_json::{Value, json};
use tokio::{ use tokio::{
io::{AsyncReadExt, BufReader}, io::{AsyncReadExt, BufReader},
@ -33,6 +39,7 @@ use tokio::{
task::JoinSet, task::JoinSet,
}; };
use tokio_postgres::NoTls;
use uuid::Uuid; use uuid::Uuid;
use crate::{ use crate::{
@ -58,11 +65,11 @@ pub struct AppState {
// cached_country_names: Vec<&'static str>, // cached_country_names: Vec<&'static str>,
configures: gcm::Configure, configures: gcm::Configure,
repo: Arc<Mutex<Repository>>, repo: Arc<Mutex<Repository>>,
// commit external db
postgres: Arc<Mutex<tokio_postgres::Client>>,
redis: RedisPool, redis: RedisPool,
// save already fetched path, further calls should fetch from redis instead. // save already fetched path, further calls should fetch from redis instead.
fetched: Vec<String>, fetched: Vec<String>,
// queue
enqueue_tx: Sender<CacheJob>,
} }
impl AppState { impl AppState {
@ -349,12 +356,18 @@ struct Signature {
email: String, email: String,
} }
#[debug_handler]
async fn commit_handler( async fn commit_handler(
State(state): State<AppState>, State(state): State<AppState>,
// request body as multipart/form-data // request body as multipart/form-data
mut payload: Multipart, mut payload: Multipart,
) -> impl IntoResponse { ) -> impl IntoResponse {
// operate on repo
let op_repo = state
.get_config("GIT_REPO_LOCAL_DEST")
.cloned()
.unwrap_or_default()
.replace("./", "");
// Extract multipart fields // Extract multipart fields
let mut path: Option<String> = None; let mut path: Option<String> = None;
let mut signature_username: Option<String> = None; let mut signature_username: Option<String> = None;
@ -362,6 +375,8 @@ async fn commit_handler(
let mut message: Option<String> = None; let mut message: Option<String> = None;
let mut file_bytes: Option<Vec<u8>> = None; let mut file_bytes: Option<Vec<u8>> = None;
let mut ref_message: Option<String> = None;
let mut file_mappings: HashMap<String, Vec<u8>> = HashMap::new(); let mut file_mappings: HashMap<String, Vec<u8>> = HashMap::new();
let mut filex_map: HashMap<String, Vec<u8>> = HashMap::new(); let mut filex_map: HashMap<String, Vec<u8>> = HashMap::new();
let mut pathx_map: HashMap<String, String> = HashMap::new(); let mut pathx_map: HashMap<String, String> = HashMap::new();
@ -420,7 +435,8 @@ async fn commit_handler(
}); });
} }
"file" => { "file" => {
file_bytes = Some(match field.bytes().await { let _file_bytes = field.bytes().await;
file_bytes = Some(match _file_bytes {
Ok(b) => b.to_vec(), Ok(b) => b.to_vec(),
Err(e) => { Err(e) => {
return ( return (
@ -430,6 +446,17 @@ async fn commit_handler(
} }
}); });
} }
"ref" => {
ref_message = Some(match field.text().await {
Ok(r) => r,
Err(e) => {
return (
axum::http::StatusCode::BAD_REQUEST,
Json(json!({"error": format!("Failed to read ref field: {}", e)})),
);
}
})
}
filex if name.starts_with("file") => match field.bytes().await { filex if name.starts_with("file") => match field.bytes().await {
Ok(fx) => { Ok(fx) => {
@ -495,8 +522,8 @@ async fn commit_handler(
// Create signature // Create signature
let signature = Signature { let signature = Signature {
username: signature_username, username: signature_username.clone(),
email: signature_email, email: signature_email.clone(),
}; };
info!("sig: {signature:?}"); info!("sig: {signature:?}");
@ -557,6 +584,44 @@ async fn commit_handler(
info!("commit success! [{commit_oid}]"); info!("commit success! [{commit_oid}]");
} }
// TODO: save to db
let mut plock = state.postgres.lock().await;
let tx = match plock.transaction().await {
Ok(x) => x,
Err(_) => {
return (
axum::http::StatusCode::CREATED,
Json(
json!({"result": format!("{commit_oid}"), "reason": "CannotConnect_CommitMayBeLost"}),
),
);
}
};
let insert_commit = tx.execute("INSERT INTO recovery_commit (commit_hash, commit_user, email, repo, ref) VALUES ($1, $2, $3, $4, $5)", &[
&commit_oid.to_string(),
&signature_username.clone(),
&signature_email.clone(),
&op_repo,
&ref_message.unwrap_or_default(),
]).await;
if insert_commit.is_ok() {
match tx.commit().await {
Ok(_) => {
info!("successfully inserted into postgres");
}
Err(e) => {
error!("commit postgres failed: {e}");
}
}
} else {
error!("failed to commit to postgres & rolling back ...");
if let Err(rollback_error) = tx.rollback().await {
error!("failed to rollback, {rollback_error}");
}
}
( (
axum::http::StatusCode::OK, axum::http::StatusCode::OK,
Json(json!({"result": format!("{commit_oid}")})), Json(json!({"result": format!("{commit_oid}")})),
@ -712,11 +777,34 @@ async fn commit_file_content(
) -> Result<git2::Oid, Box<dyn std::error::Error>> { ) -> Result<git2::Oid, Box<dyn std::error::Error>> {
let repo_guard = repo.lock().await; let repo_guard = repo.lock().await;
let blob_oid = repo_guard.blob(content)?; let workdir = repo_guard.workdir().ok_or_else(|| {
git2::Error::from_str("Repository must have a working directory for this strategy")
})?;
let mut index = repo_guard.index()?; let full_path = if Path::new(path).is_absolute() {
Path::new(path).to_path_buf()
} else {
workdir.join(path)
};
if let Some(parent) = full_path.parent() {
std::fs::create_dir_all(parent)?;
}
std::fs::write(&full_path, content)?;
let target_ref = format!("refs/heads/{branch}"); let target_ref = format!("refs/heads/{branch}");
let relative_git_path = if full_path.is_absolute() {
full_path.strip_prefix(workdir).map_err(|_| {
git2::Error::from_str(
"The provided path is outside of the repository working directory",
)
})?
} else {
Path::new(path)
};
let mut index = repo_guard.index()?;
let parent_commit = match repo_guard.find_reference(&target_ref) { let parent_commit = match repo_guard.find_reference(&target_ref) {
Ok(reference) => { Ok(reference) => {
let commit = reference.peel_to_commit()?; let commit = reference.peel_to_commit()?;
@ -726,20 +814,8 @@ async fn commit_file_content(
Err(_) => None, Err(_) => None,
}; };
index.add(&git2::IndexEntry { index.add_path(relative_git_path)?;
ctime: git2::IndexTime::new(0, 0), index.write()?;
mtime: git2::IndexTime::new(0, 0),
dev: 0,
ino: 0,
mode: 0o100644,
uid: 0,
gid: 0,
file_size: content.len() as u32,
id: blob_oid,
flags: 0,
flags_extended: 0,
path: path.as_bytes().to_vec(),
})?;
let tree_oid = index.write_tree()?; let tree_oid = index.write_tree()?;
let tree = repo_guard.find_tree(tree_oid)?; let tree = repo_guard.find_tree(tree_oid)?;
@ -753,14 +829,17 @@ async fn commit_file_content(
let oid = repo_guard.commit( let oid = repo_guard.commit(
//"refs/heads/master" //"refs/heads/master"
Some(&target_ref), None, &sig, &sig, message, &tree, &parents,
&sig,
&sig,
message,
&tree,
&parents,
)?; )?;
repo_guard.reference(&target_ref, oid, true, "api commit log update")?;
if let Ok(mut head) = repo_guard.head() {
if head.name() == Some(&target_ref) {
repo_guard.set_head(&target_ref)?;
}
}
info!("commit oid: {oid}"); info!("commit oid: {oid}");
Ok(oid) Ok(oid)
@ -774,9 +853,59 @@ async fn commit_multiple_file_contents(
branch: String, branch: String,
) -> Result<git2::Oid, Box<dyn std::error::Error>> { ) -> Result<git2::Oid, Box<dyn std::error::Error>> {
let repo_guard = repo.lock().await; let repo_guard = repo.lock().await;
let mut index = repo_guard.index()?;
let workdir = repo_guard.workdir().ok_or_else(|| {
git2::Error::from_str("Repository must have a working directory for this strategy")
})?;
// Pre-calculate and normalize paths into a standard vector so we don't have
// path-resolution discrepancies between loops.
// Keeps: (Absolute Path on Disk, Relative Path for Git Index, Content Buffer)
let mut processing_queue: Vec<(PathBuf, PathBuf, &[u8])> = Vec::new();
for (raw_path, content) in &file_mapping {
let path_obj = Path::new(raw_path);
// 1. Compute a clean, guaranteed absolute path for writing to disk
let full_path = if path_obj.is_absolute() {
path_obj.to_path_buf()
} else {
// This handles files starting with "./", "../", or raw filenames safely
workdir.join(path_obj)
};
// Canonicalize or cleanly normalize the path syntax relative to the workdir
// to strip out relative quirks like "." or ".." components
let clean_full_path = match full_path.canonicalize() {
Ok(p) => p,
Err(_) => full_path, // Fallback if file doesn't exist yet on disk
};
// 2. Compute a strict relative path matching how Git sees the workspace
let relative_git_path = clean_full_path
.strip_prefix(workdir)
.map_err(|_| {
git2::Error::from_str(
"A provided path lives outside the repository working directory",
)
})?
.to_path_buf();
processing_queue.push((clean_full_path, relative_git_path, content));
}
// Loop 1: Safely write all processed files to disk first
for (full_path, _, content) in &processing_queue {
if let Some(parent) = full_path.parent() {
std::fs::create_dir_all(parent)?;
}
std::fs::write(full_path, content)?;
}
let mut index = repo_guard.index()?;
let target_ref = format!("refs/heads/{branch}"); let target_ref = format!("refs/heads/{branch}");
// Loop 2: Hydrate index from previous branch commit tree
let parent_commit = match repo_guard.find_reference(&target_ref) { let parent_commit = match repo_guard.find_reference(&target_ref) {
Ok(reference) => { Ok(reference) => {
let commit = reference.peel_to_commit()?; let commit = reference.peel_to_commit()?;
@ -786,24 +915,15 @@ async fn commit_multiple_file_contents(
Err(_) => None, Err(_) => None,
}; };
for (_, (path, file)) in file_mapping.iter().enumerate() { // Loop 3: Stage all files using the matching pre-calculated relative path
let blob_oid = repo_guard.blob(file.as_slice())?; for (_, relative_git_path, _) in &processing_queue {
index.add(&git2::IndexEntry { index.add_path(relative_git_path)?;
ctime: git2::IndexTime::new(0, 0),
mtime: git2::IndexTime::new(0, 0),
dev: 0,
ino: 0,
mode: 0o100644,
uid: 0,
gid: 0,
file_size: file.len() as u32,
id: blob_oid,
flags: 0,
flags_extended: 0,
path: path.as_bytes().to_vec(),
})?;
} }
// Flush changes to .git/index
index.write()?;
// Create tree layout from updated index state
let tree_oid = index.write_tree()?; let tree_oid = index.write_tree()?;
let tree = repo_guard.find_tree(tree_oid)?; let tree = repo_guard.find_tree(tree_oid)?;
@ -814,17 +934,20 @@ async fn commit_multiple_file_contents(
None => vec![], None => vec![],
}; };
let oid = repo_guard.commit( // Commit changes to the object database
//"refs/heads/master" let oid = repo_guard.commit(None, &sig, &sig, message, &tree, &parents)?;
Some(&target_ref),
&sig,
&sig,
message,
&tree,
&parents,
)?;
info!("commit oid: {oid}"); // Advance branch reference explicitly
repo_guard.reference(&target_ref, oid, true, "api multi-file commit update")?;
// Sync active workspace reference state if checking out the branch locally
if let Ok(head) = repo_guard.head() {
if head.name() == Some(&target_ref) {
repo_guard.set_head(&target_ref)?;
}
}
info!("Multi-file transaction successfully committed. OID: {oid}");
Ok(oid) Ok(oid)
} }
@ -909,6 +1032,102 @@ async fn pull_handler(State(state): State<AppState>) -> impl IntoResponse {
) )
} }
#[derive(Debug, Serialize, Deserialize)]
struct ReplayPayload {
commit_hashes: Vec<String>,
}
async fn replay_handler(
State(state): State<AppState>,
Json(payload): Json<ReplayPayload>,
) -> impl IntoResponse {
let repo = state.repo.clone();
let commit_hashes: Vec<&str> = payload.commit_hashes.iter().map(|x| x.as_str()).collect();
if let Err(e) = replay(repo, &commit_hashes.as_slice()).await {
return (
axum::http::StatusCode::INTERNAL_SERVER_ERROR,
Json(json!({"error": e.to_string()})),
);
}
(
axum::http::StatusCode::OK,
Json(json!({"result": "replay completed"})),
)
}
async fn replay(
repo: Arc<Mutex<Repository>>,
commit_hashes: &[&str],
) -> Result<(), Box<dyn std::error::Error>> {
let repo_guard = repo.lock().await;
let initial_head = repo_guard.head()?.peel_to_commit()?;
info!(
"Starting transaction replay. Initial HEAD: {}",
initial_head.id()
);
for (index, hash) in commit_hashes.iter().enumerate() {
let oid = Oid::from_str(hash)?;
let commit_to_apply = repo_guard.find_commit(oid)?;
info!(
"#[{}/{}] Doing {} - \"{}\"",
index + 1,
commit_hashes.len(),
&hash[0..7],
commit_to_apply.summary().unwrap_or("[No summary]")
);
let mut cherrypick_opts = git2::CherrypickOptions::new();
repo_guard.cherrypick(&commit_to_apply, Some(&mut cherrypick_opts))?;
let mut repo_index = repo_guard.index()?;
if repo_index.has_conflicts() {
error!("[CONFLICTED] at commit {}, aborting ...", &hash[0..7]);
let current_head_commit = repo_guard.head()?.peel_to_commit()?;
repo_guard.reset(current_head_commit.as_object(), ResetType::Hard, None)?;
return Err(format!("index_{}/conflicted", index).into());
}
let tree_id = repo_index.write_tree()?;
let tree = repo_guard.find_tree(tree_id)?;
let original_author = commit_to_apply.author();
let author = git2::Signature::now(
original_author.name().unwrap_or("Transaction Processor"),
original_author
.email()
.unwrap_or("processor@local.internal"),
)?;
let current_head = repo_guard.head()?.peel_to_commit()?;
let _new_commit_oid = repo_guard.commit(
Some("HEAD"),
&author,
&author,
&format!(
"Replay transaction: {}",
commit_to_apply.summary().unwrap_or("")
),
&tree,
&[&current_head],
)?;
repo_guard.cleanup_state()?;
info!("applied success!");
}
info!("all transaction applied successfully!");
Ok(())
}
// Pull is fetch + merge // Pull is fetch + merge
async fn pull( async fn pull(
config: gcm::Configure, config: gcm::Configure,
@ -971,10 +1190,6 @@ async fn health_handler(State(_): State<AppState>) -> impl IntoResponse {
pub async fn run(config: gcm::Configure) -> gcm::StandardResult { pub async fn run(config: gcm::Configure) -> gcm::StandardResult {
// REDIS POOL // REDIS POOL
//
//
//
//
let manager = RedisConnectionManager::new(format!( let manager = RedisConnectionManager::new(format!(
"redis://{}:{}", "redis://{}:{}",
config.get("REDIS_URI").unwrap_or(&"".to_string()), config.get("REDIS_URI").unwrap_or(&"".to_string()),
@ -987,7 +1202,33 @@ pub async fn run(config: gcm::Configure) -> gcm::StandardResult {
.await .await
.expect("failed to build redis pool"); .expect("failed to build redis pool");
let (enqueue_tx, enqueue_rx) = channel::<CacheJob>(8); // let connect_str = "host=localhost user=postgres password=my_password dbname=my_database";
let connect_str = config
.get("POSTGRES_CONN")
.expect("failed to connect postgres");
info!("connect config: {connect_str}");
let (mut client, connection) = tokio_postgres::connect(connect_str, NoTls).await?;
tokio::spawn(async move {
if let Err(e) = connection.await {
error!("connection postgres error: {e}");
}
});
info!("Creating 'commit' table ...");
client
.execute(
"CREATE TABLE IF NOT EXISTS recovery_commit (
id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
commit_hash TEXT NOT NULL,
commit_user TEXT NOT NULL,
email TEXT NOT NULL,
repo TEXT NOT NULL,
ref TEXT NOT NULL,
commit_at TIMESTAMPTZ DEFAULT NOW(),
expire_at TIMESTAMPTZ DEFAULT NOW() + interval '1 hour'
)",
&[],
)
.await?;
let state = AppState { let state = AppState {
// cached_country_names: common::valid_country_name(), // cached_country_names: common::valid_country_name(),
@ -995,9 +1236,9 @@ pub async fn run(config: gcm::Configure) -> gcm::StandardResult {
repo: Arc::new(Mutex::new(Repository::open( repo: Arc::new(Mutex::new(Repository::open(
config.get("GIT_REPO_LOCAL_DEST").unwrap_or(&"".to_string()), config.get("GIT_REPO_LOCAL_DEST").unwrap_or(&"".to_string()),
)?)), )?)),
postgres: Arc::new(Mutex::new(client)),
redis: redis_pool.clone(), redis: redis_pool.clone(),
fetched: Vec::new(), fetched: Vec::new(),
enqueue_tx: enqueue_tx.clone(),
}; };
let app = Router::new() let app = Router::new()
@ -1007,6 +1248,8 @@ pub async fn run(config: gcm::Configure) -> gcm::StandardResult {
.route("/push", get(push_handler)) .route("/push", get(push_handler))
.route("/pull", get(pull_handler)) .route("/pull", get(pull_handler))
.route("/health", get(health_handler)) .route("/health", get(health_handler))
.route("/replay", post(replay_handler))
.layer(DefaultBodyLimit::max(100 * 1024 * 1024))
// .route("/healthz", get(reg::health)) // .route("/healthz", get(reg::health))
.with_state(state); .with_state(state);

View file

@ -61,6 +61,7 @@ async fn main() -> gcm::StandardResult {
.get("CONFIG_VERSION") .get("CONFIG_VERSION")
.expect("config version not defined") .expect("config version not defined")
); );
info!("RUNNING: {:?}", config.get("PUBLIC_PORT"));
app::run(config.clone()).await?; app::run(config.clone()).await?;
Ok(()) Ok(())