commit 0a48113d38eda15b7923f9fc71d4e2fe1792bbd7 Author: JMARyA Date: Wed Jan 22 17:45:12 2025 +0100 init diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..947272d --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,624 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "anstream" +version = "0.6.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" + +[[package]] +name = "anstyle-parse" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" +dependencies = [ + "anstyle", + "once_cell", + "windows-sys 0.59.0", +] + +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "clap" +version = "4.5.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8eb5e908ef3a6efbe1ed62520fb7287959888c88485abe072543190ecc66783" +dependencies = [ + "clap_builder", +] + +[[package]] +name = "clap_builder" +version = "4.5.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96b01801b5fc6a0a232407abc821660c9c6d25a1cafc0d4f85f29fb8d9afc121" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_lex" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" + +[[package]] +name = "colorchoice" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" + +[[package]] +name = "crossterm" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e64e6c0fbe2c17357405f7c758c1ef960fce08bdfb2c03d88d2a18d7e09c4b67" +dependencies = [ + "bitflags 1.3.2", + "crossterm_winapi", + "libc", + "mio", + "parking_lot", + "signal-hook", + "signal-hook-mio", + "winapi", +] + +[[package]] +name = "crossterm_winapi" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" +dependencies = [ + "winapi", +] + +[[package]] +name = "dyn-clone" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125" + +[[package]] +name = "fuzzy-matcher" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54614a3312934d066701a80f20f15fa3b56d67ac7722b39eea5b4c9dd1d66c94" +dependencies = [ + "thread_local", +] + +[[package]] +name = "fxhash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +dependencies = [ + "byteorder", +] + +[[package]] +name = "g" +version = "0.1.0" +dependencies = [ + "clap", + "inquire", + "serde_json", + "subprocess", + "yansi", +] + +[[package]] +name = "inquire" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fddf93031af70e75410a2511ec04d49e758ed2f26dad3404a934e0fb45cc12a" +dependencies = [ + "bitflags 2.8.0", + "crossterm", + "dyn-clone", + "fuzzy-matcher", + "fxhash", + "newline-converter", + "once_cell", + "unicode-segmentation", + "unicode-width", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + +[[package]] +name = "itoa" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" + +[[package]] +name = "libc" +version = "0.2.169" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" + +[[package]] +name = "lock_api" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f" + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "mio" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys 0.48.0", +] + +[[package]] +name = "newline-converter" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b6b097ecb1cbfed438542d16e84fd7ad9b0c76c8a65b7f9039212a3d14dc7f" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "once_cell" +version = "1.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" + +[[package]] +name = "parking_lot" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets 0.52.6", +] + +[[package]] +name = "proc-macro2" +version = "1.0.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "redox_syscall" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" +dependencies = [ + "bitflags 2.8.0", +] + +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "serde" +version = "1.0.217" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.217" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.137" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "930cfb6e6abf99298aaad7d29abbef7a9999a9a8806a40088f55f0dcec03146b" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "signal-hook" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-mio" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd" +dependencies = [ + "libc", + "mio", + "signal-hook", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" +dependencies = [ + "libc", +] + +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "subprocess" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c2e86926081dda636c546d8c5e641661049d7562a68f5488be4a1f7f66f6086" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "syn" +version = "2.0.96" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5d0adab1ae378d7f53bdebc67a39f1f151407ef230f0ce2883572f5d8985c80" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thread_local" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +dependencies = [ + "cfg-if", + "once_cell", +] + +[[package]] +name = "unicode-ident" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" + +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[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", + "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_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[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_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[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_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[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_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[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_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "yansi" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..13be6fc --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "g" +version = "0.1.0" +edition = "2024" + +[dependencies] +clap = { version = "4.5.26", features = ["cargo"] } +inquire = "0.7.5" +serde_json = "1.0.137" +subprocess = "0.2.9" +yansi = "1.0.1" diff --git a/PKGBUILD b/PKGBUILD new file mode 100644 index 0000000..5f02947 --- /dev/null +++ b/PKGBUILD @@ -0,0 +1,38 @@ +# Maintainer: JMARyA +pkgname=g +pkgver=main +pkgrel=1 +pkgdesc="git workflow wrapper" +arch=('x86_64') +url="https://git.hydrar.de/jmarya/g" +license=("MIT") +depends=("git" "serie" "ripgrep" "onefetch") +makedepends=("rustup" "git") +source=("${pkgname}::git+https://git.hydrar.de/jmarya/g.git") +sha256sums=("SKIP") + +pkgver() { + cd "$srcdir/$pkgname" + echo "$(date +%Y.%m.%d)_$(git rev-parse --short HEAD)" +} + +prepare() { + cd "$srcdir/$pkgname" + rustup default nightly + cargo fetch +} + +build() { + cd "$srcdir/$pkgname" + cargo build --release +} + +check() { + cd "$srcdir/$pkgname" + cargo test --release +} + +package() { + cd "$srcdir/$pkgname" + install -Dm755 "target/release/g" "$pkgdir/usr/bin/g" +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..e067f1c --- /dev/null +++ b/README.md @@ -0,0 +1,101 @@ +# `g`: The TOP G of Git Wrappers + +Welcome to `g`, the ultimate Git wrapper for the hustlers. Inspired by the TOP G mindset, `g` ensures your version control game is faster, cleaner, and dripping with efficiency. Whether you're stacking branches like cash or committing to greatness, `g` is your ticket into the Real Git. + +## Why choose `g`? +- **Efficiency is key** - Git commands can feel like a 9-to-5 grind. `g` is here to automate the hustle. +- **Stay focused** - Just like in Hustlers University, distractions are the enemy. `g` keeps you in the zone. +- **Work like a TOP G** - This isn’t just version control; it’s a lifestyle. Be the alpha in your repo. + +## Commands +### Repository Management +- `g bootstrap ` + + Initialize a new repository from a base repository. + + > You don’t start from scratch; you leverage the foundation like a true hustler. + +### Branching +- `g branch` + + Display all branches. + + > Know your assets, track your investments. This is your repo empire. + +- `g new ` + + Create a new branch. + + > In the world of branching, growth is king. + +- `g remove ` + + Remove a branch. + + > Trim the weak branches so the strong ones can thrive. + +### Syncing +- `g fetch` + + Run `git fetch --prune`. + + > Prune like a gardener who only grows money trees. Keep the repo tight and right. + +- `g pull` + + Run `git pull --prune`. + + > Pull harder than a Bugatti in first gear. Stay synced, stay winning. + +- `g push` + + Push changes to a remote repository. + + > Send your code to the cloud like it’s your private jet. + +### Logs and Status +- `g log` + + Display the commit log. + + > The hustle doesn’t lie. Your commit history is your legacy. + +- `g status` + + Show the current repository status. + + > Stay on top of your repo. TOP Gs don’t leave things to chance. + +### Commit Management +- `g todo` + + Show open TODOs within the repository. + + > Keep the numbers rising, except this one. + +- `g save` + + Create a WIP (Work In Progress) commit. + + > Sometimes you gotta save it now to cash in later. + +- `g commit [-a, --all] [-d, --done] [-i, --interactive] [-m, --message ]` + + Commit changes. + + - `-all` adds all changed files to Git. + + - `--done` refuses to commit with open TODOs. + + - `--interactive` creates gitmoji commits with an interactive menu. + + - `--message` sets the commit message directly. + + > Seal the deal with a strong message. Every commit should flex." + +### Adding Files +- `g add ` + + Stage files for the next commit. + + > Add value, one file at a time. diff --git a/src/args.rs b/src/args.rs new file mode 100644 index 0000000..365db17 --- /dev/null +++ b/src/args.rs @@ -0,0 +1,89 @@ +use clap::{arg, command}; + +pub fn get_args() -> clap::ArgMatches { + command!() + .about("Git wrapper") + .subcommand( + command!("bootstrap") + .about("Bootstrap a new repository from base repository") + .arg(arg!( "Base repository").required(true)) + .arg(arg!( "Repository name").required(true)) + .alias("bs"), + ) + .subcommand( + command!("todo") + .about("Show open TODOs in repository") + .alias("t"), + ) + .subcommand( + command!("new") + .about("Create a new branch") + .arg(arg!( "Branch name").required(true)) + .alias("n"), + ) + .subcommand( + command!("commit") + .about("Commit current changes") + .alias("c") + .arg(arg!(-a --all "Add all changed files to commit")) + .arg(arg!(-d --done "Only allow commiting if no TODOs are present")) + .arg( + arg!(-i --interactive "Write interactive commit message with gitmoji standard") + .conflicts_with("message"), + ) + .arg(arg!(-m --message "Commit message").required(false)), + ) + .subcommand( + command!("remove") + .about("Remove a branch") + .arg(arg!( "Branch name").required(true)) + .alias("r"), + ) + .subcommand(command!("branch").about("Show branches").alias("b")) + .subcommand( + command!() + .name("push") + .about("Push to a remote") + .arg(arg!(-f --force "Force push")) + .alias("p"), + ) + .subcommand( + command!() + .name("status") + .about("Show git status") + .alias("stat") + .alias("stats"), + ) + .subcommand( + command!() + .name("add") + .about("Add files to git") + .alias("a") + .arg(arg!( "File to add").required(true)), + ) + .subcommand( + command!() + .name("fetch") + .about("Run git fetch --prune") + .alias("f"), + ) + .subcommand( + command!() + .name("pull") + .about("Run git pull --prune") + .alias("p"), + ) + .subcommand( + command!() + .name("log") + .about("Show git commit log") + .alias("l"), + ) + .subcommand( + command!() + .name("save") + .about("Create a WIP commit") + .alias("s"), + ) + .get_matches() +} diff --git a/src/git.rs b/src/git.rs new file mode 100644 index 0000000..6b23b5b --- /dev/null +++ b/src/git.rs @@ -0,0 +1,260 @@ +use subprocess::Exec; +use yansi::{Color, Paint}; + +use crate::{ + NO_COMMIT_REGEX, no_commit_amount, precommit::rust_pre_commit, read_stdout, show_rg, + todos_amount, +}; + +/// Check if the current repository has a remote set up +pub fn has_remote() -> bool { + let e = Exec::cmd("git").arg("remote").arg("-v"); + let str = read_stdout(e); + !str.is_empty() +} + +/// Create a new branch and switch to it in the current repository +pub fn create_new_branch(branch: &str) { + let mut git = Exec::cmd("git") + .arg("checkout") + .arg("-b") + .arg(branch) + .popen() + .unwrap(); + git.wait().unwrap(); + if has_remote() { + let mut git = Exec::cmd("git") + .arg("push") + .arg("-u") + .arg("origin") + .arg(branch) + .popen() + .unwrap(); + git.wait().unwrap(); + } +} + +/// Push the current repository to remote +pub fn push(force: bool) { + let mut cmd = Exec::cmd("git") + .arg("push") + .arg("--set-upstream") + .arg("origin"); + + if force { + cmd = cmd.arg("--force-with-lease"); + } + + cmd.popen().unwrap().wait().unwrap(); +} + +/// Delete a branch from the current repository +pub fn delete_branch(branch: &str) { + let mut git = Exec::cmd("git") + .arg("branch") + .arg("-d") + .arg(branch) + .popen() + .unwrap(); + git.wait().unwrap(); + if has_remote() { + let mut git = Exec::cmd("git") + .arg("push") + .arg("origin") + .arg("--delete") + .arg(branch) + .popen() + .unwrap(); + git.wait().unwrap(); + } +} + +/// Get the title of the last commit +pub fn last_commit() -> String { + let e = Exec::cmd("git").arg("log").arg("-1").arg("--pretty=%B"); + read_stdout(e) +} + +/// Get all files affected by last commit +pub fn last_commit_files() -> Vec { + let file_str = read_stdout( + Exec::cmd("git") + .arg("diff-tree") + .arg("--no-commit-id") + .arg("--name-only") + .arg("-r") + .arg("HEAD"), + ); + + file_str.split('\n').map(|x| x.to_string()).collect() +} + +/// Add a file to git +pub fn git_add(file: &str) { + Exec::cmd("git") + .arg("add") + .arg(file) + .popen() + .unwrap() + .wait() + .unwrap(); +} + +/// Get a Vec of programming languages used in this repository +pub fn get_languages() -> Vec { + let e: Exec = Exec::cmd("onefetch").arg("-o").arg("json"); + let json_str = read_stdout(e); + if let Result::::Ok(json) = serde_json::from_str(&json_str) { + let languages = (|| { + let fields = json.as_object()?.get("infoFields")?.as_array()?; + let langs = fields + .iter() + .find(|x| x.as_object().unwrap().contains_key("LanguagesInfo"))?; + let langs = langs + .as_object()? + .get("LanguagesInfo")? + .as_object()? + .get("languagesWithPercentage")? + .as_array()?; + Some( + langs + .iter() + .map(|x| { + x.as_object() + .unwrap() + .get("language") + .unwrap() + .as_str() + .unwrap() + .to_string() + }) + .collect(), + ) + })(); + + languages.unwrap_or_default() + } else { + Vec::new() + } +} + +/// Commit changes to the repository. +pub fn commit(all: bool, done: bool, msg: &str) { + // Work In Progress Save Commit + if last_commit().as_str() == "WIP" { + // Get files affected by commit + let last_commit_files = last_commit_files(); + + // Reset last commit + Exec::cmd("git") + .arg("reset") + .arg("HEAD~1") + .popen() + .unwrap() + .wait() + .unwrap(); + + // Readd files affected by commit + for file in last_commit_files { + git_add(&file); + } + } + + // Check TODOs + let todos = todos_amount(); + + if todos > 0 { + println!( + "{}: Repository still contains {todos} TODO entries.", + "Warning".paint(Color::Yellow) + ); + + if done { + println!( + "{}", + "💀 Make sure to close them before commiting or we can't rest in peace..." + .paint(Color::Red) + ); + std::process::exit(1); + } + } + + // Impossible commits + if no_commit_amount() > 0 { + println!("{}", "Unable to commit because of:".paint(Color::Red)); + show_rg(NO_COMMIT_REGEX); + std::process::exit(1); + } + + // Laguage specific pre-commit hooks + for lang in get_languages() { + match lang.as_str() { + "Rust" => { + rust_pre_commit(); + } + _ => {} + } + } + + if all { + Exec::cmd("git") + .arg("add") + .arg("-A") + .popen() + .unwrap() + .wait() + .unwrap(); + } + + Exec::cmd("git") + .arg("commit") + .arg("-m") + .arg(msg) + .popen() + .unwrap() + .wait() + .unwrap(); +} + +/// Fetch remote state +pub fn fetch() { + Exec::cmd("git") + .arg("fetch") + .arg("--prune") + .popen() + .unwrap() + .wait() + .unwrap(); +} + +/// Pull remote changes +pub fn pull() { + Exec::cmd("git") + .arg("pull") + .arg("--prune") + .popen() + .unwrap() + .wait() + .unwrap(); +} + +/// Bootstrap a git repository using a base template +pub fn bootstrap(base: &str, name: &str) { + Exec::cmd("git") + .arg("clone") + .arg(base) + .arg(name) + .popen() + .unwrap() + .wait() + .unwrap(); + std::fs::remove_dir_all(std::path::Path::new(name).join(".git")).unwrap(); + Exec::cmd("git") + .arg("init") + .arg("--quiet") + .cwd(std::path::Path::new(name)) + .popen() + .unwrap() + .wait() + .unwrap(); +} diff --git a/src/gitmoji.rs b/src/gitmoji.rs new file mode 100644 index 0000000..21566cd --- /dev/null +++ b/src/gitmoji.rs @@ -0,0 +1,360 @@ +pub fn select_from_list String>( + prompt: &str, + lst: Vec<&str>, + quit: bool, + then: F, +) -> String { + match inquire::Select::new(prompt, lst).prompt() { + Ok(val) => then(val), + Err(_) => { + if quit { + eprintln!("Nothing selected"); + std::process::exit(1); + } + + String::new() + } + } +} + +pub fn select_gitmoji() -> String { + let topics = vec![ + "Fix", + "Refactor", + "Improve", + "Add/Update", + "Remove", + "Git", + "UI", + "Dependency", + "Security", + "Text", + "Test", + "Fun", + "Misc", + ]; + + let select = select_from_list( + "Gitmoji Intention Topic", + topics, + true, + |topic| match topic { + "Fix" => select_gitmoji_fix(), + "Improve" => select_gitmoji_improvement(), + "Remove" => select_gitmoji_remove(), + "Dependency" => select_gitmoji_dependency(), + "Test" => select_gitmoji_test(), + "Git" => select_gitmoji_git(), + "Refactor" => select_gitmoji_refactor(), + "UI" => select_gitmoji_ui(), + "Text" => select_gitmoji_text(), + "Fun" => select_gitmoji_fun(), + "Security" => select_gitmoji_security(), + "Add/Update" => select_gitmoji_add_update(), + _ => select_gitmoji_misc(), + }, + ); + + if select.is_empty() { + return select_gitmoji(); + } + + select +} + +pub fn select_gitmoji_fix() -> String { + let options = vec![ + "🐛 Fix a bug", + "🩹 Simple fix for a non-critical issue", + "🚑️ Critical hotfix", + "✏️ Fix typos", + "💚 Fix CI Build", + "🔒️ Fix security or privacy issues", + "🚨 Fix compiler / linter warnings", + "🥅 Catch errors", + ]; + + let res = select_from_list("Gitmoji Intention", options, false, |selected| { + selected.split_whitespace().next().unwrap().to_string() + }); + + if res.is_empty() { + return select_gitmoji(); + } + + res +} + +pub fn select_gitmoji_improvement() -> String { + let options = vec![ + "✨ Introduce new features", + "🎨 Improve structure / format of the code", + "⚡️ Improve performance", + "🧑‍💻 Improve developer experience", + "🚸 Improve user experience / usability", + "♿️ Improve accessibility", + "🔍️ Improve SEO", + ]; + + let res = select_from_list("Gitmoji Intention", options, false, |selected| { + selected.split_whitespace().next().unwrap().to_string() + }); + + if res.is_empty() { + return select_gitmoji(); + } + + res +} + +pub fn select_gitmoji_remove() -> String { + let options = vec!["🔥 Remove code or files", "⚰️ Remove dead code"]; + + let res = select_from_list("Gitmoji Intention", options, false, |selected| { + selected.split_whitespace().next().unwrap().to_string() + }); + + if res.is_empty() { + return select_gitmoji(); + } + + res +} + +pub fn select_gitmoji_dependency() -> String { + let options = vec![ + "➖ Remove a dependency", + "➕ Add a dependency", + "⬇️ Downgrade dependencies", + "⬆️ Upgrade dependencies", + "📌 Pin dependencies to specific versions", + ]; + + let res = select_from_list("Gitmoji Intention", options, false, |selected| { + selected.split_whitespace().next().unwrap().to_string() + }); + + if res.is_empty() { + return select_gitmoji(); + } + + res +} + +pub fn select_gitmoji_test() -> String { + let options = vec![ + "✅ Add, update, or pass tests", + "🧪 Add a failing test", + "🤡 Mock things", + "⚗️ Perform experiments", + ]; + + let res = select_from_list("Gitmoji Intention", options, false, |selected| { + selected.split_whitespace().next().unwrap().to_string() + }); + + if res.is_empty() { + return select_gitmoji(); + } + + res +} + +pub fn select_gitmoji_fun() -> String { + let options = vec![ + "🍻 Write code drunkenly", + "🍃 Write code high", + "❄ Write code on coke", + "🍄 Write code on psychedelics", + "🥚 Add or update an easter egg", + "💩 Write bad code that needs to be improved", + ]; + + let res = select_from_list("Gitmoji Intention", options, false, |selected| { + selected.split_whitespace().next().unwrap().to_string() + }); + + if res.is_empty() { + return select_gitmoji(); + } + + res +} + +pub fn select_gitmoji_git() -> String { + let options = vec![ + "⏪️ Revert changes", + "🔀 Merge branches", + "🔖 Release / Version tags", + "🙈 Add or update a .gitignore file", + ]; + + let res = select_from_list("Gitmoji Intention", options, false, |selected| { + selected.split_whitespace().next().unwrap().to_string() + }); + + if res.is_empty() { + return select_gitmoji(); + } + + res +} + +pub fn select_gitmoji_refactor() -> String { + let options = vec![ + "♻️ Refactor code", + "🗑️ Deprecate code that needs to be cleaned up", + "🚚 Move or rename resources (e.g.: files, paths, routes)", + "🍱 Add or update assets", + "👽️ Update code due to external API changes", + "💡 Add or update comments in source code", + "🏷️ Add or update types", + ]; + + let res = select_from_list("Gitmoji Intention", options, false, |selected| { + selected.split_whitespace().next().unwrap().to_string() + }); + + if res.is_empty() { + return select_gitmoji(); + } + + res +} + +pub fn select_gitmoji_ui() -> String { + let options = vec![ + "💄 Add or update the UI and style files", + "📱 Work on responsive design", + "💫 Add or update animations and transitions", + ]; + + let res = select_from_list("Gitmoji Intention", options, false, |selected| { + selected.split_whitespace().next().unwrap().to_string() + }); + + if res.is_empty() { + return select_gitmoji(); + } + + res +} + +pub fn select_gitmoji_text() -> String { + let options = vec![ + "📝 Add or update documentation", + "🌐 Internationalization and localization", + "💬 Add or update text and literals", + "👥 Add or update contributor(s)", + "📄 Add or update license", + "🔊 Add or update logs", + "🔇 Remove logs", + ]; + + let res = select_from_list("Gitmoji Intention", options, false, |selected| { + selected.split_whitespace().next().unwrap().to_string() + }); + + if res.is_empty() { + return select_gitmoji(); + } + + res +} + +pub fn select_gitmoji_security() -> String { + let options = vec![ + "🛂 Work on code related to authorization, roles and permissions", + "🔐 Add or update secrets", + "🦺 Add or update code related to validation", + ]; + + let res = select_from_list("Gitmoji Intention", options, false, |selected| { + selected.split_whitespace().next().unwrap().to_string() + }); + + if res.is_empty() { + return select_gitmoji(); + } + + res +} + +pub fn select_gitmoji_add_update() -> String { + let options = vec![ + "👷 Add or update CI build system", + "🔧 Add or update configuration files", + "🔨 Add or update development scripts", + "📦️ Add or update compiled files or packages", + "🚩 Add, update, or remove feature flags", + "🩺 Add or update healthcheck", + "🧵 Add or update code related to multithreading or concurrency", + "🌱 Add or update seed files", + "📸 Add or update snapshots", + "📈 Add or update analytics or track code", + "👔 Add or update business logic", + ]; + + let res = select_from_list("Gitmoji Intention", options, false, |selected| { + selected.split_whitespace().next().unwrap().to_string() + }); + + if res.is_empty() { + return select_gitmoji(); + } + + res +} + +pub fn select_gitmoji_misc() -> String { + let options = vec![ + "🎉 Begin a project", + "🚀 Deploy stuff", + "🚧 Work in progress", + "💥 Introduce breaking changes", + "🗃️ Perform database related changes", + "🏗️ Make architectural changes", + "🧱 Infrastructure related changes", + "💸 Add sponsorships or money related infrastructure", + "🧐 Data exploration/inspection", + ]; + + let res = select_from_list("Gitmoji Intention", options, false, |selected| { + selected.split_whitespace().next().unwrap().to_string() + }); + + if res.is_empty() { + return select_gitmoji(); + } + + res +} + +pub struct CommitMessage { + gitmoji: String, + title: String, + body: Option, +} + +impl CommitMessage { + pub const fn new(gitmoji: String, title: String, body: Option) -> Self { + CommitMessage { + gitmoji, + title, + body, + } + } + + pub fn to_msg(&self) -> String { + let mut msg = self.gitmoji.clone(); + msg.push_str(&format!(" {}", self.title)); + + if let Some(body) = &self.body { + if !body.is_empty() { + msg.push_str(&format!("\n\n{body}")); + } + } + + msg + } +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..91ca737 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,154 @@ +use git::{bootstrap, commit, create_new_branch, delete_branch, fetch, git_add, pull, push}; +use gitmoji::{CommitMessage, select_gitmoji}; +use std::io::Read; +use subprocess::{Exec, Redirection}; + +mod args; +mod git; +mod gitmoji; +mod precommit; + +pub fn read_stdout(e: Exec) -> String { + let mut p = e.stdout(Redirection::Pipe).popen().unwrap(); + let mut str = String::new(); + p.stdout.as_mut().unwrap().read_to_string(&mut str).unwrap(); + str.trim().to_string() +} + +const TODO_REGEX: &str = r" (todo|unimplemented|refactor|wip|fix)(:|!|\n|\ :)"; +const NO_COMMIT_REGEX: &str = r"(NOCOMMIT|ENSURE: )"; + +pub fn no_commit_amount() -> u64 { + rg_matches(NO_COMMIT_REGEX) +} + +pub fn todos_amount() -> u64 { + rg_matches(TODO_REGEX) +} + +pub fn rg_matches(regex: &str) -> u64 { + let mut cmd = Exec::cmd("rg") + .arg("-i") + .arg("--json") + .arg("--multiline") + .arg(regex) + .stdout(Redirection::Pipe) + .popen() + .unwrap(); + cmd.wait().unwrap(); + let mut str = String::new(); + cmd.stdout + .as_mut() + .unwrap() + .read_to_string(&mut str) + .unwrap(); + let last_line = str.lines().last().unwrap(); + let val: serde_json::Value = serde_json::from_str(last_line).unwrap(); + let ret = (|| { + val.as_object() + .unwrap() + .get("data")? + .as_object()? + .get("stats")? + .as_object()? + .get("matches")? + .as_number() + .unwrap() + .as_i64() + })(); + ret.unwrap_or(0) as u64 +} + +pub fn show_rg(regex: &str) { + Exec::cmd("rg") + .arg("-i") + .arg("--multiline") + .arg(regex) + .popen() + .unwrap() + .wait() + .unwrap(); +} + +pub fn show_todos() { + println!("{} todos found.", todos_amount()); + show_rg(TODO_REGEX); +} + +fn main() { + let args = args::get_args(); + + match args.subcommand() { + Some(("bootstrap", bs_args)) => { + let base = bs_args.get_one::("BASE").unwrap(); + let repo = bs_args.get_one::("NEW").unwrap(); + bootstrap(base, repo); + } + Some(("new", new_args)) => { + let branch: &String = new_args.get_one("BRANCH").unwrap(); + create_new_branch(branch); + } + Some(("remove", rm_args)) => { + let branch: &String = rm_args.get_one("BRANCH").unwrap(); + delete_branch(branch); + } + Some(("branch", _)) => { + Exec::cmd("git") + .arg("branch") + .popen() + .unwrap() + .wait() + .unwrap(); + } + Some(("push", push_args)) => { + let force = push_args.get_flag("force"); + push(force); + } + Some(("commit", commit_args)) => { + let all = commit_args.get_flag("all"); + let done = commit_args.get_flag("done"); + let msg: Option<&String> = commit_args.get_one("message"); + let interactive = commit_args.get_flag("interactive"); + + if interactive || msg.is_none() { + let gitmoji = select_gitmoji(); + let title = inquire::prompt_text("Commit Title").unwrap(); + let body = inquire::Text::new("Commit Description") + .with_page_size(5) + .prompt(); + let msg = CommitMessage::new(gitmoji, title, body.ok()).to_msg(); + commit(all, done, &msg); + } else { + commit(all, done, msg.unwrap()); + } + } + Some(("save", _)) => { + commit(true, false, "WIP"); + } + Some(("fetch", _)) => { + fetch(); + } + Some(("pull", _)) => { + pull(); + } + Some(("todo", _)) => { + show_todos(); + } + Some(("log", _)) => { + Exec::cmd("serie").popen().unwrap().wait().unwrap(); + } + Some(("status", _)) => { + Exec::cmd("git") + .arg("status") + .popen() + .unwrap() + .wait() + .unwrap(); + } + Some(("add", args)) => { + let file: &String = args.get_one("FILE").unwrap(); + git_add(file); + } + _ => {} + } +} diff --git a/src/precommit.rs b/src/precommit.rs new file mode 100644 index 0000000..5941b6e --- /dev/null +++ b/src/precommit.rs @@ -0,0 +1,22 @@ +use crate::{git::git_add, read_stdout}; +use subprocess::Exec; + +pub fn rust_pre_commit() { + println!("Running cargo fmt"); + let e = Exec::cmd("cargo").arg("fmt").arg("--verbose"); + let out = read_stdout(e); + + for line in out.lines() { + println!("line {line}"); + if line.starts_with("rustfmt") { + let mut split: Vec<_> = line.split_whitespace().collect(); + split.reverse(); + split.pop(); + split.pop(); + split.pop(); + split.reverse(); + let file = split.join(" "); + git_add(&file); + } + } +}