This commit is contained in:
JMARyA 2025-01-22 17:45:12 +01:00
commit 0a48113d38
Signed by: jmarya
GPG key ID: 901B2ADDF27C2263
10 changed files with 1660 additions and 0 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/target

624
Cargo.lock generated Normal file
View file

@ -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"

11
Cargo.toml Normal file
View file

@ -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"

38
PKGBUILD Normal file
View file

@ -0,0 +1,38 @@
# Maintainer: JMARyA <jmarya@hydrar.de>
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"
}

101
README.md Normal file
View file

@ -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 isnt just version control; its a lifestyle. Be the alpha in your repo.
## Commands
### Repository Management
- `g bootstrap <BASE> <REPO>`
Initialize a new repository from a base repository.
> You dont 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 <BRANCH>`
Create a new branch.
> In the world of branching, growth is king.
- `g remove <BRANCH>`
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 its your private jet.
### Logs and Status
- `g log`
Display the commit log.
> The hustle doesnt lie. Your commit history is your legacy.
- `g status`
Show the current repository status.
> Stay on top of your repo. TOP Gs dont 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 <MSG>]`
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 <FILE>`
Stage files for the next commit.
> Add value, one file at a time.

89
src/args.rs Normal file
View file

@ -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> "Base repository").required(true))
.arg(arg!(<NEW> "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> "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 <MSG> "Commit message").required(false)),
)
.subcommand(
command!("remove")
.about("Remove a branch")
.arg(arg!(<BRANCH> "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> "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()
}

260
src/git.rs Normal file
View file

@ -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<String> {
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<String> {
let e: Exec = Exec::cmd("onefetch").arg("-o").arg("json");
let json_str = read_stdout(e);
if let Result::<serde_json::Value, _>::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();
}

360
src/gitmoji.rs Normal file
View file

@ -0,0 +1,360 @@
pub fn select_from_list<F: FnOnce(&str) -> 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<String>,
}
impl CommitMessage {
pub const fn new(gitmoji: String, title: String, body: Option<String>) -> 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
}
}

154
src/main.rs Normal file
View file

@ -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::<String>("BASE").unwrap();
let repo = bs_args.get_one::<String>("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);
}
_ => {}
}
}

22
src/precommit.rs Normal file
View file

@ -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);
}
}
}