diff --git a/.gitignore b/.gitignore index 7bf286f..ee5854e 100644 --- a/.gitignore +++ b/.gitignore @@ -161,4 +161,8 @@ cython_debug/ # option (not recommended) you can uncomment the following to ignore the entire idea folder. #.idea/ -.vscode/ \ No newline at end of file +.vscode/ + +# Added by cargo + +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..236e5b8 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,800 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "anstream" +version = "0.6.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" + +[[package]] +name = "anstyle-parse" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys", +] + +[[package]] +name = "anyhow" +version = "1.0.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0e0fee31ef5ed1ba1316088939cea399010ed7731dba877ed44aeb407a75ea" + +[[package]] +name = "bitflags" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" + +[[package]] +name = "cc" +version = "1.2.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aebf35691d1bfb0ac386a69bac2fde4dd276fb618cf8bf4f5318fe285e821bb2" +dependencies = [ + "find-msvc-tools", + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "clap" +version = "4.5.59" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5caf74d17c3aec5495110c34cc3f78644bfa89af6c8993ed4de2790e49b6499" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.59" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "370daa45065b80218950227371916a1633217ae42b2715b2287b606dcd618e24" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a92793da1a46a5f2a02a6f4c46c6496b28c43638adea8306fcb0caa1634f24e5" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a822ea5bc7590f9d40f1ba12c0dc3c2760f3482c6984db1573ad11031420831" + +[[package]] +name = "colorchoice" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasip2", +] + +[[package]] +name = "git-varcs" +version = "2.0.0-alpha.0" +dependencies = [ + "anyhow", + "clap", + "git2", + "regex", + "serde", + "toml", +] + +[[package]] +name = "git2" +version = "0.20.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b88256088d75a56f8ecfa070513a775dd9107f6530ef14919dac831af9cfe2b" +dependencies = [ + "bitflags", + "libc", + "libgit2-sys", + "log", + "openssl-probe", + "openssl-sys", + "url", +] + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "icu_collections" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" +dependencies = [ + "displaydoc", + "potential_utf", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" + +[[package]] +name = "icu_properties" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" + +[[package]] +name = "icu_provider" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "indexmap" +version = "2.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + +[[package]] +name = "jobserver" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +dependencies = [ + "getrandom", + "libc", +] + +[[package]] +name = "libc" +version = "0.2.182" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112" + +[[package]] +name = "libgit2-sys" +version = "0.18.3+1.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9b3acc4b91781bb0b3386669d325163746af5f6e4f73e6d2d630e09a35f3487" +dependencies = [ + "cc", + "libc", + "libssh2-sys", + "libz-sys", + "openssl-sys", + "pkg-config", +] + +[[package]] +name = "libssh2-sys" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "220e4f05ad4a218192533b300327f5150e809b54c4ec83b5a1d91833601811b9" +dependencies = [ + "cc", + "libc", + "libz-sys", + "openssl-sys", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "libz-sys" +version = "1.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15d118bbf3771060e7311cc7bb0545b01d08a8b4a7de949198dec1fa0ca1c0f7" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "litemap" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" + +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + +[[package]] +name = "memchr" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" + +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + +[[package]] +name = "openssl-probe" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" + +[[package]] +name = "openssl-sys" +version = "0.9.111" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82cab2d520aa75e3c58898289429321eb788c3106963d0dc886ec7a5f4adc321" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "pkg-config" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + +[[package]] +name = "potential_utf" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" +dependencies = [ + "zerovec", +] + +[[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 = "quote" +version = "1.0.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[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 = "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_spanned" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "876ac351060d4f882bb1032b6369eb0aef79ad9df1ea8bc404874d8cc3d0cd98" +dependencies = [ + "serde_core", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "syn" +version = "2.0.116" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3df424c70518695237746f84cede799c9c58fcb37450d7b23716568cc8bc69cb" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tinystr" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "toml" +version = "1.1.0+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8195ca05e4eb728f4ba94f3e3291661320af739c4e43779cbdfae82ab239fcc" +dependencies = [ + "indexmap", + "serde_core", + "serde_spanned", + "toml_datetime", + "toml_parser", + "toml_writer", + "winnow", +] + +[[package]] +name = "toml_datetime" +version = "1.1.0+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97251a7c317e03ad83774a8752a7e81fb6067740609f75ea2b585b569a59198f" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_parser" +version = "1.1.0+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2334f11ee363607eb04df9b8fc8a13ca1715a72ba8662a26ac285c98aabb4011" +dependencies = [ + "winnow", +] + +[[package]] +name = "toml_writer" +version = "1.1.0+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d282ade6016312faf3e41e57ebbba0c073e4056dab1232ab1cb624199648f8ed" + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "url" +version = "2.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "wasip2" +version = "1.0.2+wasi-0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" +dependencies = [ + "wit-bindgen", +] + +[[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.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "winnow" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a90e88e4667264a994d34e6d1ab2d26d398dcdca8b7f52bec8668957517fc7d8" + +[[package]] +name = "wit-bindgen" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" + +[[package]] +name = "writeable" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" + +[[package]] +name = "yoke" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerofrom" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerotrie" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..f67235a --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "git-varcs" +version = "2.0.0-alpha.0" +edition = "2024" + +[[bin]] +name = "git-vms" +path = "src/main.rs" + +[dependencies] +anyhow = "1.0.101" +clap = { version = "4.5.59", features = ["derive"] } +git2 = "0.20.4" +regex = "1.12.3" +serde = { version = "1.0.228", features = ["derive"] } +toml = "1.0.7" diff --git a/README.md b/README.md index 4df1e3c..d7a4cc3 100644 --- a/README.md +++ b/README.md @@ -160,3 +160,31 @@ For further details or to explore more usage options, refer to the [Typer Docume ## Development 1. Create a virtual environment and install both requirement-files. + +### Rust Components + +To build and run the rust components, use standard Cargo commands from the project root: + +```bash +# Build the project +cargo build + +# Run the project +cargo run +``` + +The binary will be compiled into `target/debug/git-vms`. + +**Symlinking Binaries** + +For rapid development, you can symlink the compiled binaries to a location in your `$PATH` so you can run them like any other git command: + +```bash +ln -s $(pwd)/target/debug/git-vms /path/to/your/bin/git-vms +``` + +After that, you can run the binary directly: + +```bash +git vms --version +``` diff --git a/src/cli.rs b/src/cli.rs new file mode 100644 index 0000000..ca6c482 --- /dev/null +++ b/src/cli.rs @@ -0,0 +1,36 @@ +use clap::{Parser, Subcommand}; + +#[derive(Parser)] +#[command(version, about)] +pub struct Cli { + #[command(subcommand)] + pub command: Commands, +} + +#[derive(Subcommand)] +pub enum Commands { + /// Initialize variation management support in the git repository + Init, + /// Associate feature metadata and stage files to the git index + Add { + /// Files to tag with feature meta and stage to git + files: Vec, + /// Feature to associate the file(s) with + #[arg(short, long)] + feature: String, + }, + /// Commit staged changes with feature metadata + Commit { + /// Commit message + #[arg(short, long)] + message: String, + }, + /// Derive a variant with the features specified in fog.toml + Derive { + /// Name of the variant entry in fog.toml + name: String, + /// Refresh the variant if it already exists + #[arg(short, long)] + refresh: bool, + }, +} diff --git a/src/commands.rs b/src/commands.rs new file mode 100644 index 0000000..1e70c70 --- /dev/null +++ b/src/commands.rs @@ -0,0 +1,4 @@ +pub mod add; +pub mod commit; +pub mod derive; +pub mod init; diff --git a/src/commands/add.rs b/src/commands/add.rs new file mode 100644 index 0000000..35140dc --- /dev/null +++ b/src/commands/add.rs @@ -0,0 +1,31 @@ +use std::path::Path; + +use anyhow::{Context, Result}; +use git2::Repository; + +use crate::meta::{read_meta, write_meta}; + +pub fn run(repo: &Repository, files: &[String], feature: &str) -> Result<()> { + let feature_meta_path = repo.path().join("varcs").join("FEATUREMETA"); + let current_index_feature = read_meta(&feature_meta_path).unwrap_or_default(); + + if current_index_feature.is_empty() { + write_meta(&feature_meta_path, feature)?; + } else if feature != current_index_feature { + eprintln!( + "Current index is already associated with feature '{}'. Commit it before continuing.", + current_index_feature + ); + return Ok(()); + } + + let mut index = repo.index()?; + for file in files { + index + .add_path(Path::new(file)) + .with_context(|| format!("Failed to stage path '{}'", file))?; + } + index.write()?; + + Ok(()) +} diff --git a/src/commands/commit.rs b/src/commands/commit.rs new file mode 100644 index 0000000..3de78fc --- /dev/null +++ b/src/commands/commit.rs @@ -0,0 +1,48 @@ +use anyhow::{Context, Result}; +use git2::Repository; + +use crate::meta::{read_meta, write_meta}; + +pub fn run(repo: &Repository, message: &str) -> Result<()> { + let feature_meta_path = repo.path().join("varcs").join("FEATUREMETA"); + let current_index_feature = read_meta(&feature_meta_path).unwrap_or_default(); + + if current_index_feature.is_empty() { + eprintln!("No associated feature metadata. Aborting."); + return Ok(()); + } + + let mut index = repo.index()?; + + let tree_oid = index + .write_tree() + .context("failed to write tree from index")?; + let tree = repo.find_tree(tree_oid)?; + + let sig = repo + .signature() + .context("failed to determine git signature")?; + index.write()?; + + let parent_commit = match repo.head() { + Ok(head) => { + let commit = head + .peel_to_commit() + .context("HEAD does not point to a commit")?; + Some(commit) + } + Err(_) => None, + }; + + let meta_msg = format!( + "{}\n\nCHANGE_ID: {}\nFEATURE: {}\n", + message, tree_oid, current_index_feature + ); + match parent_commit { + Some(parent) => repo.commit(Some("HEAD"), &sig, &sig, &meta_msg, &tree, &[&parent])?, + None => repo.commit(Some("HEAD"), &sig, &sig, &meta_msg, &tree, &[])?, + }; + + write_meta(&feature_meta_path, "")?; + Ok(()) +} diff --git a/src/commands/derive.rs b/src/commands/derive.rs new file mode 100644 index 0000000..ce83491 --- /dev/null +++ b/src/commands/derive.rs @@ -0,0 +1,182 @@ +use std::collections::{HashMap, HashSet}; +use std::fs::File; +use std::io::{BufRead, BufReader}; +use std::path::{Path, PathBuf}; + +use anyhow::{Context, Result, anyhow}; +use git2::{BlameOptions, Commit, ObjectType, Oid, Repository, Status, Tree}; +use regex::Regex; + +use crate::config::{Variant, read_config}; + +pub fn run(repo: &Repository, name: &str, refresh: bool) -> Result<()> { + let statuses = repo.statuses(None)?; + for entry in statuses.iter() { + let s = entry.status(); + if s != Status::CURRENT { + println!("Repository is not clean!"); + return Ok(()); + } + } + + let variant_spec = get_variant_spec(repo, name).context("Failed to get variant spec")?; + let ref_name = format!("refs/heads/variant/{}", variant_spec.name); + + let variant_ref = repo.find_reference(&ref_name).ok(); + + let parent_refs = if let Some(variant_ref) = variant_ref { + if refresh { + let commit = variant_ref.peel_to_commit()?; + vec![commit] + } else { + anyhow::bail!("Variant already exists. Use --refresh to update it."); + } + } else { + vec![] + }; + let parent_refs: Vec<&Commit> = parent_refs.iter().collect(); + + let target_features: HashSet = variant_spec.features.iter().cloned().collect(); + let sig = repo.signature()?; + + let head = repo.head()?; + let tree = head.peel_to_tree()?; + + let variant_tree_oid = build_variant_tree(repo, &tree, Path::new(""), &target_features)?; + let variant_tree = repo.find_tree(variant_tree_oid)?; + + repo.commit( + Some(&ref_name), + &sig, + &sig, + &format!( + "variant derivation with features {:?}", + variant_spec.features + ), + &variant_tree, + &parent_refs, + )?; + Ok(()) +} + +fn get_variant_spec(repo: &Repository, name: &str) -> Result { + let config = read_config(repo)?; + let variant = config + .variants + .get(name) + .ok_or_else(|| anyhow!("no variant found with name '{}'", name))?; + Ok(variant.clone()) +} + +fn build_variant_tree( + repo: &Repository, + tree: &Tree, + base_path: &Path, + target_features: &HashSet, +) -> Result { + let mut builder = repo.treebuilder(None)?; + for entry in tree.iter() { + let name = entry + .name() + .ok_or_else(|| anyhow!("tree entry without valid UTF-8 name"))?; + let full_path = base_path.join(name); + + match entry.kind() { + Some(ObjectType::Blob) => { + let content = process_file(repo, &full_path, target_features) + .with_context(|| format!("Failed to process file {}", full_path.display()))?; + let oid = repo.blob(content.as_bytes())?; + builder.insert(name, oid, entry.filemode())?; + } + Some(ObjectType::Tree) => { + let subtree = repo.find_tree(entry.id())?; + let subtree_oid = build_variant_tree(repo, &subtree, &full_path, target_features) + .with_context(|| { + format!("Failed to process directory: {}", full_path.display()) + })?; + builder.insert(name, subtree_oid, entry.filemode())?; + } + _ => {} + } + } + + Ok(builder.write()?) +} + +fn process_file( + repo: &Repository, + name: &PathBuf, + target_features: &HashSet, +) -> Result { + let file = File::open(name)?; + let reader = BufReader::new(file); + let mut lines: Vec = reader.lines().collect::>()?; + + let mut commit_cache: HashMap = HashMap::new(); + + let mut blame_opts = BlameOptions::new(); + let blame = repo.blame_file(Path::new(name), Some(&mut blame_opts))?; + + for (i, line) in lines.iter_mut().enumerate() { + let line_no = i + 1; + + let hunk = blame + .get_line(line_no) + .context("Cant get line info in hunk")?; + + let oid = hunk.final_commit_id(); + + let contains_feature = commit_cache.entry(oid).or_insert_with(|| { + let commit = repo.find_commit(oid).unwrap(); + let summary = commit.body().unwrap(); + extract_feature_meta(summary).is_some_and(|feature| target_features.contains(feature)) + }); + + if !*contains_feature { + *line = String::new(); + } + } + + // lines.retain(|line| !line.is_empty()); + Ok(lines.join("\n")) +} + +// TODO: building each time can be expensive. Look for an alternative +fn extract_feature_meta(commit: &str) -> Option<&str> { + let re = Regex::new(r"(?m)^FEATURE:\s*(?P[^\n]+)$").unwrap(); + re.captures(commit) + .and_then(|caps| caps.name("feature").map(|m| m.as_str())) +} + +#[cfg(test)] +mod tests { + use super::extract_feature_meta; + + #[test] + fn extracts_feature_when_present() { + let msg = "chore: init\n\nFeature: core"; + assert_eq!(extract_feature_meta(msg), Some("core")); + } + + #[test] + fn extracts_feature_with_other_metadata() { + let msg = "\ +chore: initialize fog support in repo + +ChangeId: de9378ec963973b367aa0ad706fa006af3ce65da +Feature: core"; + assert_eq!(extract_feature_meta(msg), Some("core")); + } + + #[test] + fn returns_none_when_no_feature() { + let msg = "fix: missing semicolon"; + assert_eq!(extract_feature_meta(msg), None); + } + + #[test] + fn ignores_invalid_format() { + let msg = "Feature core"; + assert_eq!(extract_feature_meta(msg), None); + } +} diff --git a/src/commands/init.rs b/src/commands/init.rs new file mode 100644 index 0000000..60dc5f1 --- /dev/null +++ b/src/commands/init.rs @@ -0,0 +1,32 @@ +use std::fs; +use std::io::Write; + +use anyhow::{Context, Result}; +use git2::Repository; + +const DEFAULT_CONFIG: &str = r#"# git-varcs configuration file +# You can define variants and associate features here +"#; + +pub fn run(repo: &Repository) -> Result<()> { + let base_path = repo + .path() + .parent() + .context("Repository has no parent directory")?; + let config_path = base_path.join("varcs.toml"); + let meta_path = base_path.join(".git/varcs"); + + fs::create_dir(meta_path)?; + + let mut file = fs::OpenOptions::new() + .write(true) + .create_new(true) + .open(&config_path) + .with_context(|| format!("Failed to create config file at path: {:?}", config_path))?; + + file.write(DEFAULT_CONFIG.as_bytes()) + .with_context(|| format!("Failed to write default config to: {:?}", config_path))?; + + println!("Initialized config file at {}", config_path.display()); + Ok(()) +} diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000..bf0b62b --- /dev/null +++ b/src/config.rs @@ -0,0 +1,28 @@ +use std::collections::HashMap; +use std::fs; + +use anyhow::{Context, Result}; +use git2::Repository; +use serde::Deserialize; + +#[derive(Deserialize)] +pub struct Config { + pub variants: HashMap, +} + +#[derive(Deserialize, Clone)] +pub struct Variant { + pub name: String, + pub features: Vec, +} + +pub fn read_config(repo: &Repository) -> Result { + let base_path = repo + .path() + .parent() + .context("Repository has no parent directory")?; + let config_path = base_path.join("varcs.toml"); + let data = fs::read_to_string(config_path)?; + let config = toml::from_str(&data)?; + Ok(config) +} diff --git a/src/main.py b/src/main.py deleted file mode 100644 index 4e3d766..0000000 --- a/src/main.py +++ /dev/null @@ -1,5 +0,0 @@ -# &begin[test] -def test(): ... - - -# &end[test] diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..12fa3bb --- /dev/null +++ b/src/main.rs @@ -0,0 +1,33 @@ +mod cli; +mod commands; +mod config; +mod meta; + +use anyhow::{Context, Result}; +use clap::Parser; +use git2::Repository; + +use cli::{Cli, Commands}; +use commands::{add, commit, derive, init}; + +fn main() -> Result<()> { + let args = Cli::parse(); + let repo = Repository::discover(".").context("Not in a git repository")?; + + match args.command { + Commands::Init => { + init::run(&repo).context("Failed to initialize varcs support")?; + } + Commands::Add { files, feature } => { + add::run(&repo, &files, &feature).context("Failed staging files with meta")?; + } + Commands::Commit { message } => { + commit::run(&repo, &message).context("Failed commiting current index")?; + } + Commands::Derive { name, refresh } => { + derive::run(&repo, &name, refresh) + .with_context(|| format!("Failed to derive variant '{}'", name))?; + } + } + Ok(()) +} diff --git a/src/meta.rs b/src/meta.rs new file mode 100644 index 0000000..04f4b8a --- /dev/null +++ b/src/meta.rs @@ -0,0 +1,14 @@ +use std::fs; +use std::path::PathBuf; + +use anyhow::Result; + +pub fn read_meta(path: &PathBuf) -> Result { + let data = fs::read_to_string(path)?; + Ok(data) +} + +pub fn write_meta(path: &PathBuf, content: &str) -> Result<()> { + fs::write(path, content)?; + Ok(()) +}