diff --git a/.gitignore b/.gitignore index b4148c2..fd93d04 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,5 @@ skills-lock.json # already existing elements were commented out #/target +*.mp4 +.mimocode/ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..f50fef1 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Jheison Morales + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index b892dcd..758976d 100644 --- a/README.md +++ b/README.md @@ -113,6 +113,11 @@ gitkit attributes init gitkit config apply defaults ``` +## Documentation + +Full documentation lives in [`docs/`](docs/): installation, quick start, +hooks, ignore & attributes, config presets, builds and the complete CLI reference. + --- ## `gitkit status` @@ -316,4 +321,5 @@ MIT --- -Made with ❤️ by [JheisonMB](https://github.com/JheisonMB) and [UniverLab](https://github.com/UniverLab) +An experiment of [UniverLab](https://github.com/UniverLab) — an open computational laboratory. +Made with ❤️ by [JheisonMB](https://github.com/JheisonMB) diff --git a/assets/demo.gif b/assets/demo.gif index 7a82971..004babd 100644 Binary files a/assets/demo.gif and b/assets/demo.gif differ diff --git a/demo/demo.rec b/demo/demo.rec new file mode 100644 index 0000000..d9cb977 --- /dev/null +++ b/demo/demo.rec @@ -0,0 +1,339 @@ +{"demostage":{"captions":[],"demo":{"name":"demo","output_dir":"./dist","prompt":"\\[\\e[1;32m\\]gitkit@univerlab\\[\\e[0m\\]:\\[\\e[1;34m\\]~\\[\\e[0m\\]$ "},"duration":25.248482625999998,"faithful":false,"focuses":[[0.121114331,"main"]],"layout":{"background":"#0b0f14","font_family":"IBM Plex Mono","fps":15,"height":1080,"line_height":1.2000000476837158,"panes":[{"font_family":"monospace","font_size":16,"height":1080,"id":"main","type":"terminal","width":1440,"x":0,"y":0}],"width":1440},"timeline":[{"action":"focus","pane":"main"},{"action":"type","human_salt":true,"text":"gitkit clone https://github.com/Univerlab/gitkit.git"},{"action":"wait","duration_ms":200},{"action":"keypress","key":"enter"},{"action":"wait","duration_ms":200},{"action":"keypress","key":"enter"},{"action":"wait_for_quiet","quiet_ms":1500},{"action":"keypress","key":"down"},{"action":"wait","duration_ms":349},{"action":"keypress","key":"down"},{"action":"wait","duration_ms":1316},{"action":"type","human_salt":true,"text":" "},{"action":"wait","duration_ms":2295},{"action":"keypress","key":"down"},{"action":"wait","duration_ms":2500},{"action":"type","human_salt":true,"text":" "},{"action":"wait","duration_ms":200},{"action":"keypress","key":"enter"},{"action":"wait_for_quiet","quiet_ms":500},{"action":"type","human_salt":true,"text":" "},{"action":"wait","duration_ms":200},{"action":"keypress","key":"enter"},{"action":"wait","duration_ms":200},{"action":"keypress","key":"enter"},{"action":"wait_for_quiet","quiet_ms":1500},{"action":"keypress","key":"down"},{"action":"wait","duration_ms":180},{"action":"keypress","key":"down"},{"action":"wait","duration_ms":180},{"action":"keypress","key":"down"},{"action":"wait","duration_ms":174},{"action":"keypress","key":"down"},{"action":"wait","duration_ms":183},{"action":"keypress","key":"down"},{"action":"wait","duration_ms":452},{"action":"type","human_salt":true,"text":" "},{"action":"wait","duration_ms":200},{"action":"keypress","key":"enter"},{"action":"wait_for_quiet","quiet_ms":1500},{"action":"type","human_salt":true,"text":"y"},{"action":"wait","duration_ms":200},{"action":"keypress","key":"enter"},{"action":"wait_for_quiet","quiet_ms":1500},{"action":"type","human_salt":true,"text":"n"},{"action":"wait","duration_ms":200},{"action":"keypress","key":"enter"},{"action":"wait_for_quiet","quiet_ms":1500},{"action":"terminate"}],"typing":{"base_ms":80,"salt_ms":15}},"env":{"TERM":"xterm-256color"},"height":54,"title":"demo","version":2,"width":144} +[0.000149573,"o","\r\n\u001b[?2004l\r"] +[0.000341838,"o","\u001b]697;OSCUnlock=e7d40ddbbcf646adb2a3f991e2653752\u0007\u001b]697;Dir=/home/jheisonmblivecom\u0007"] +[0.000395938,"o","\u001b]697;Shell=bash\u0007\u001b]697;ShellPath=/usr/bin/bash\u0007"] +[0.000432054,"o","\u001b]697;WSLDistro=Ubuntu\u0007\u001b]697;PID=384284\u0007"] +[0.000454295,"o","\u001b]697;ExitCode=0\u0007"] +[0.000503406,"o","\u001b]697;TTY=/dev/pts/3\u0007\u001b]697;Log=\u0007"] +[0.000550283,"o","\u001b]697;User=jheisonmblivecom\u0007"] +[0.002205377,"o","\u001b]697;OSCLock=e7d40ddbbcf646adb2a3f991e2653752\u0007\u001b]697;PreExec\u0007"] +[0.090440045,"o","\u001b[?2004h\u001b]697;StartPrompt\u0007\u001b[1;32mgitkit@univerlab\u001b[0m:\u001b[1;34m~\u001b[0m$ \u001b]697;EndPrompt\u0007\u001b]697;NewCmd=e7d40ddbbcf646adb2a3f991e2653752\u0007"] +[0.205396331,"o","g"] +[0.29245563,"o","i"] +[0.374586576,"o","t"] +[0.456665947,"o","k"] +[0.542812294,"o","i"] +[0.616967685,"o","t"] +[0.708014315,"o"," "] +[0.78715641,"o","c"] +[0.876269577,"o","l"] +[0.954341305,"o","o"] +[1.044411149,"o","n"] +[1.121504251,"o","e"] +[1.21558598,"o"," "] +[1.304678009,"o","h"] +[1.371741417,"o","t"] +[1.4529130160000001,"o","t"] +[1.51900404,"o","p"] +[1.584327246,"o","s"] +[1.665471969,"o",":"] +[1.737550897,"o","/"] +[1.816689335,"o","/"] +[1.8877970039999998,"o","g"] +[1.97192102,"o","i"] +[2.049988074,"o","t"] +[2.145095254,"o","h"] +[2.213184113,"o","u"] +[2.306281667,"o","b"] +[2.371359412,"o","."] +[2.4474508090000002,"o","c"] +[2.537576432,"o","o"] +[2.62266493,"o","m"] +[2.6917531649999997,"o","/"] +[2.761850503,"o","U"] +[2.846986783,"o","n"] +[2.9410971139999997,"o","i"] +[3.006182029,"o","v"] +[3.08729468,"o","e"] +[3.167392549,"o","r"] +[3.259514481,"o","l"] +[3.337584911,"o","a"] +[3.407682461,"o","b"] +[3.48777246,"o","/"] +[3.5718810469999998,"o","g"] +[3.654973113,"o","i"] +[3.73307455,"o","t"] +[3.818157087,"o","k"] +[3.910342722,"o","i"] +[3.998390311,"o","t"] +[4.076527235,"o","."] +[4.153620093,"o","g"] +[4.221737178,"o","i"] +[4.31486321,"o","t"] +[4.516573471,"o","\r\n\u001b[?2004l\r"] +[4.518526525,"o","\r\n ◇ cloning repository...\r\n"] +[4.778916875,"o","\r\n"] +[6.3458408760000005,"o","^[[B"] +[6.545744518,"o"," ◇ clone completed ✓\r\n\r\n ◇ running gitkit init..."] +[6.545750649,"o","\r\n"] +[6.545752102,"o","\r\n"] +[6.545808827,"o","\r\n ███ █████ █████ ███ █████ \r\n ░░░ ░░███ ░░███ ░░░ ░░███ \r\n ███████ ████ ███████ ░███ █████ ████ ███████ \r\n ███░░███░░███ ░░░███░ ░███░░███ ░░███ ░░░███░ \r\n░███ ░███ ░███ ░███ ░██████░ ░███ ░███ \r\n░███ ░███ ░███ ░███ ███ ░███░░███ ░███ ░███ ███\r\n░░███████ █████ ░░█████ ████ █████ █████ ░░█████ \r\n ░░░░░███░░░░░ ░░░░░ ░░░░ ░░░░░ ░░░░░ ░░░░░ \r\n ███ ░███ \r\n░░██████ \r\n ░░░░░░ \r\n\r\n Configure your git repo"] +[6.545825247,"o","\r\n\r\n"] +[6.545958405,"o","\u001b[?25l\u001b[38;"] +[6.545974545,"o","5;10m"] +[6.545976278,"o","?\u001b["] +[6.546008636,"o","39m Saved builds available"] +[6.546024385,"o"," \r"] +[6.546026018,"o","\n"] +[6.546090539,"o","\u001b[38;5;14m>\u001b[39m \u001b[38;5;14m"] +[6.546140469,"o","Start fresh configuration\u001b[39m\r\n Use build: rust\r\n\u001b[38;5;14m[\u001b["] +[6.54615706,"o","39"] +[6.546158633,"o","m"] +[6.546169122,"o","\u001b[38;5;14"] +[6.546207593,"o","m↑↓ move enter confirm esc start fresh\u001b["] +[6.546210037,"o","39"] +[6.546227279,"o","m"] +[6.546233421,"o","\u001b[38;5;14"] +[6.546274236,"o","m]\u001b[39m\r\u001b[3"] +[6.546291188,"o","A\u001b[25"] +[6.54631933,"o","C\u001b[?25h"] +[6.546398696,"o","\u001b[?25l\u001b[25D\u001b[38;"] +[6.546415026,"o","5;10m>\u001b["] +[6.546425335,"o","39m "] +[6.546426918,"o","Saved builds available"] +[6.5464646680000005,"o"," \u001b[38;5;14mStart fresh configuration\u001b[39m"] +[6.546478824,"o","\u001b[K\r"] +[6.546485165,"o","\n\u001b[2K\r"] +[6.546526332,"o","\n\u001b[2K\r\n\u001b[2K\r\n\u001b[?25h"] +[6.546529057,"o","\u001b["] +[6.546578959,"o","3A\u001b[?25h\r\n"] +[6.5584489040000005,"o","\u001b[?25l\u001b[38;5;10m"] +[6.558466837,"o","?\u001b["] +[6.558485932,"o","39"] +[6.558504677,"o","m Hooks "] +[6.558515968,"o"," \r"] +[6.558517471,"o","\n\u001b[38;"] +[6.558526688,"o","5;14m"] +[6.558540513,"o",">"] +[6.558599425,"o","\u001b[39m \u001b["] +[6.558616176,"o","38;5;14m"] +[6.5586745010000005,"o","[x]\u001b[39m \u001b["] +[6.558691742,"o","38;5;14"] +[6.558693646,"o","mconventional-commits (commit-msg) — Validates Conventional Commits format"] +[6.558699867,"o","\u001b[39"] +[6.558715106,"o","m"] +[6.558740703,"o","\r\n "] +[6.5587622020000005,"o","[ ]"] +[6.5587716,"o"," no-secrets (pre-commit) — Detects common secret patterns in staged changes\r\n"] +[6.558783572,"o"," [ ] "] +[6.558801435,"o","branch-naming (pre-commit) — Validates branch name matches convention"] +[6.558818887,"o","\r"] +[6.558840908,"o","\n"] +[6.558842411,"o"," "] +[6.55889095,"o","[ ] Add custom hook...\r"] +[6.558911248,"o","\n\u001b[38;5;14"] +[6.55892359,"o","m[\u001b[39m"] +[6.558942986,"o","\u001b[38;5;14"] +[6.558972881,"o","m↑↓ move space select enter confirm esc skip\u001b["] +[6.558986086,"o","39"] +[6.559019548,"o","m\u001b[38;5;14m]\u001b[39m\r\u001b[5A\u001b["] +[6.559068261,"o","8C\u001b[?25h"] +[6.559147336,"o","\u001b[?25l\u001b[8D\r\n \u001b[38;5;10"] +[6.559151564,"o","m"] +[6.559167062,"o","[x]\u001b[39"] +[6.559226359,"o","m conventional-commits (commit-msg) — Validates Conventional Commits format\u001b[K\r\n\u001b[38;5;14m>\u001b[39m "] +[6.559245104,"o","\u001b[38;5;14m[ ]"] +[6.559257707,"o","\u001b[39m \u001b["] +[6.559269589,"o","38;5;14"] +[6.55928091,"o","m"] +[6.559303883,"o","no-secrets (pre-commit) — Detects common secret patterns in staged changes\u001b[39m"] +[6.559326374,"o","\u001b[K"] +[6.559327937,"o","\r\n"] +[6.559337004,"o","\r\n"] +[6.559350939,"o","\r"] +[6.559372129,"o","\n"] +[6.559373641,"o","\r\u001b["] +[6.5594073139999995,"o","5A\u001b[8"] +[6.559425908,"o","C\u001b[?25h"] +[6.759725409,"o","\u001b[?25l\u001b[8D\r\n\r\n [ ] no-secrets (pre-commit) — Detects common secret patterns in staged changes\u001b[K\r\n\u001b[38;5;14m>"] +[6.759731019,"o","\u001b[39m \u001b["] +[6.759776111,"o","38;5;14m[ ]\u001b[39m \u001b[38;5;14mbranch-naming (pre-commit) — Validates branch name matches convention\u001b[39m\u001b[K\r\n\r\n"] +[6.759780139,"o","\r\u001b[5A"] +[6.759830219,"o","\u001b[8C\u001b[?25h"] +[8.236640277,"o","\u001b[?25l\u001b[8D\r\n\r\n\r\n\u001b[38;5;14m>\u001b[39m \u001b["] +[8.236656307,"o","38;5;14m[x]\u001b[39m"] +[8.236721216,"o"," \u001b[38;5;14mbranch-naming (pre-commit) — Validates branch name matches convention\u001b[39m\u001b[K\r\n"] +[8.236724322,"o","\r\n"] +[8.236771038,"o","\r\u001b[5A\u001b[8C\u001b[?25h"] +[10.536333619,"o","\u001b[?25l\u001b[8D\r\n\r\n\r\n \u001b[38;5;10m[x]\u001b[39m branch-naming (pre-commit) — Validates branch name matches convention\u001b[K\r\n\u001b[38;5;14m>\u001b[39m \u001b[38;5;14m[ ]\u001b[39m \u001b[38;5;14mAdd custom hook...\u001b[39m\u001b[K\r\n\r\u001b[5A\u001b[8C\u001b[?25h"] +[13.189113385,"o","\u001b[?25l\u001b[8D\r\n\r\n\r\n"] +[13.189162055,"o","\r\n\u001b[38;5;14m>"] +[13.189188965,"o","\u001b[39m"] +[13.189190888,"o"," \u001b[38;5;14"] +[13.189192141,"o","m"] +[13.189241435,"o","[x]\u001b[39m \u001b[38;5;14mAdd custom hook...\u001b[39m\u001b[K\r\n"] +[13.189246013,"o","\r\u001b[5A\u001b[8C\u001b[?25h"] +[13.271271766,"o","\u001b[?25l\u001b[8D"] +[13.271276635,"o","\r\n\r"] +[13.271287104,"o","\n\r\n"] +[13.271288828,"o","\r\n"] +[13.271306771,"o","\u001b[38;5;14m"] +[13.271326808,"o",">\u001b[39"] +[13.27136025,"o","m \u001b[38;5;14m[ ]\u001b[39m \u001b[38;5;14mAdd custom hook...\u001b[39"] +[13.271371901,"o","m\u001b[K\r\n\r\u001b["] +[13.271373394,"o","5"] +[13.271380848,"o","A\u001b[8"] +[13.271410973,"o","C\u001b[?25h"] +[13.472967083,"o","\u001b[?25l\u001b[8D\u001b[38;5;10"] +[13.472971772,"o","m>\u001b[39m Hooks "] +[13.473011108,"o","\u001b[38;5;14mconventional-commits (commit-msg) — Validates Conventional Commits format, branch-naming (pre-commit) — Validates b\u001b[39m\u001b[K\r\n\u001b[38;5;14mranch name matches convention\u001b[39m\u001b[K\r\n\u001b[2K\r\n\u001b[2K"] +[13.473048246,"o","\r\n\u001b[2K\r\n\u001b[2K\r\n\u001b[?25h\u001b["] +[13.473091867,"o","4A\u001b[?25h"] +[13.473128321,"o","\r\n"] +[13.74868024,"o","\u001b[?25l\u001b[38;5;10m?\u001b[39m .gitignore templates \r\n\u001b[38;5;14m"] +[13.748686071,"o",">\u001b[39m \u001b[38;5;14m[ ]"] +[13.748688536,"o","\u001b[39"] +[13.74869628,"o","m \u001b[38;"] +[13.748726386,"o","5;14"] +[13.74872854,"o","magentic"] +[13.748769064,"o","\u001b[39m\r\n [ ] 1c\r\n [ ]"] +[13.748771399,"o"," 1c-bitrix\r\n [ ]"] +[13.748831818,"o"," a-frame\r\n [ ] actionscript\r\n [ ] ada\r\n [ ] adobe\r"] +[13.748836517,"o","\n "] +[13.748839242,"o"," [ ] advancedinstaller"] +[13.748843179,"o","\r\n "] +[13.748859239,"o"," [ ]"] +[13.748868185,"o"," adventuregamestudio\r\nv [ ]"] +[13.748876491,"o"," agda\r"] +[13.748911765999999,"o","\n\u001b[38;5;14m[\u001b[39m\u001b[38;"] +[13.748944266,"o","5;14mType to filter ↑↓ move space select enter confirm esc skip\u001b[39m\u001b[38;5;14m]"] +[13.74895191,"o","\u001b[39m"] +[13.748981394,"o","\r\u001b[11A\u001b[23C\u001b[?25h"] +[14.329412783,"o","\u001b[?25l\u001b[23D\r\n\u001b[38;5;14m>\u001b[39m \u001b["] +[14.329417772,"o","38;5;14m[x]\u001b[39"] +[14.329419786,"o","m \u001b["] +[14.329469761,"o","38;5;14magentic\u001b[39m\u001b[K\r\n\r\n\r\n\r"] +[14.329511486,"o","\n\r\n\r\n\r\n\r\n\r\n"] +[14.329514081,"o","\r"] +[14.329515363,"o","\n\r"] +[14.329521995,"o","\u001b[11A"] +[14.329561407,"o","\u001b[23C\u001b[?25h"] +[14.531228907,"o","\u001b[?25l\u001b[23D\u001b[38;5;10m>\u001b[39m .gitignore templates \u001b[38;5;14magentic\u001b[39m\u001b[K\r\n\u001b[2K\r\n\u001b[2K\r\n\u001b[2K\r\n\u001b[2K\r\n\u001b[2K\r\n\u001b[2K\r\n\u001b[2K\r\n\u001b[2K"] +[14.531233595,"o","\r\n"] +[14.531286522,"o","\u001b[2K\r\n\u001b[2K\r\n\u001b[2K\r\n\u001b[?25h\u001b[11A\u001b[?25h"] +[14.531346883,"o","\r\n\u001b[?25l"] +[14.53140446,"o","\u001b[38;5;10m?\u001b[39m .gitattributes \r\n\u001b[38;5;14m>\u001b[39m "] +[14.53141001,"o","\u001b[38;5;14m"] +[14.531429606,"o","[x]\u001b[39m "] +[14.531488323,"o","\u001b[38;5;14mline-endings ★ recommended — * text=auto eol=lf\u001b[39m\r\n [ ] binary-files — mark images, PDFs, archives as binary (no diff)\r\n\u001b[38;5;14m[\u001b[39m\u001b[38;5;14mspace select enter confirm esc skip\u001b[39m\u001b[38;5;14"] +[14.531492871,"o","m]\u001b["] +[14.531494945,"o","39"] +[14.531499293,"o","m\r"] +[14.531546909,"o","\u001b[3A\u001b[17C\u001b[?25h"] +[14.793456913,"o","\u001b[?25l\u001b[17D\u001b[38;5;10m>\u001b[39m .gitattributes \u001b[38;5;14mline-endings ★ recommended — * text=auto eol=lf\u001b[39m\u001b[K\r\n\u001b[2K\r\n\u001b[2K\r\n\u001b[2K\r\n\u001b[?25h"] +[14.793500994,"o","\u001b[3A\u001b[?25h\r\n"] +[14.79869019,"o","\u001b[?25l\u001b[38;5;10m?\u001b[39m Git config "] +[14.798693436,"o"," \r"] +[14.798706159,"o","\n\u001b[38;5;14"] +[14.798707582,"o","m>\u001b[39"] +[14.798736705,"o","m \u001b[38;5;14m"] +[14.79877211,"o","[x]\u001b[39m \u001b[38;5;14mpush.autoSetupRemote = true — auto-set upstream on first push\u001b["] +[14.798790564,"o","39m"] +[14.798822359999999,"o","\r\n \u001b[38;5;10"] +[14.798825396,"o","m"] +[14.798860149,"o","[x]\u001b[39m help.autocorrect = prompt — suggest corrections for mistyped commands [✓ already set]\r\n \u001b[38;5;10m[x]\u001b[39m diff.algorithm = histogram — cleaner diffs for moved code [✓ already set]\r"] +[14.798894703,"o","\n [ ] merge.conflictstyle = zdiff3 — show base in conflict markers\r\n \u001b[38;5;10m[x]\u001b[39m "] +[14.798934125,"o","rerere.enabled = true — remember and reuse conflict resolutions [✓ already set]\r\n [ ] core.pager = delta — beautiful syntax-highlighted diffs (requires cargo)\r\n\u001b[38;5;14m[\u001b["] +[14.798967135,"o","39m\u001b[38;5;14m↑↓ move space select enter confirm esc skip\u001b[39m\u001b[38;5;14m]\u001b[39m\r\u001b[7"] +[14.798998345,"o","A\u001b[13C\u001b[?25h"] +[16.360905838,"o","\u001b[?25l\u001b[13D\r\n \u001b[38;5;10m[x]"] +[16.360911378,"o","\u001b[39m push.autoSetupRemote = true — auto-set upstream on first push\u001b[K\r"] +[16.360924382,"o","\n\u001b[38;"] +[16.360941103,"o","5;14m>"] +[16.360953676,"o","\u001b[39m "] +[16.360962542,"o","\u001b[38;5;14m"] +[16.360971949,"o","[x]\u001b[39"] +[16.360992756999998,"o","m"] +[16.360995092,"o"," "] +[16.36100493,"o","\u001b[38;"] +[16.361058458,"o","5;14mhelp.autocorrect = prompt — suggest corrections for mistyped commands [✓ already set]\u001b[39m\u001b[K\r\n\r\n\r\n\r\n\r\n"] +[16.361061383,"o","\r"] +[16.361111254,"o","\u001b[7A\u001b[13C\u001b[?25h"] +[16.60295326,"o","\u001b[?25l\u001b[13D\r\n\r\n \u001b[38;5;10m[x]\u001b[39m help.autocorrect = prompt — suggest corrections for mistyped commands [✓ already set]\u001b[K\r"] +[16.602958269,"o","\n\u001b["] +[16.602960203,"o","38;5;14"] +[16.602975932,"o","m>\u001b[39m \u001b["] +[16.603035902,"o","38;5;14m[x]\u001b[39m \u001b[38;5;14m"] +[16.603106429,"o","diff.algorithm = histogram — cleaner diffs for moved code [✓ already set]\u001b[39m\u001b[K\r\n\r\n\r\n\r\n\r\u001b[7A\u001b[13C\u001b[?25h"] +[16.845051357,"o","\u001b[?25l\u001b[13D\r\n\r\n\r\n "] +[16.845057058,"o"," "] +[16.845076453,"o","\u001b[38;"] +[16.845108822,"o","5;10m[x]\u001b[39m diff.algorithm = histogram — cleaner diffs for moved code [✓ already set]\u001b[K\r\n"] +[16.845154379,"o","\u001b[38;5;14m>\u001b[39m \u001b[38;5;14m[ ]\u001b["] +[16.845159268,"o","39m \u001b[38;"] +[16.845218746,"o","5;14mmerge.conflictstyle = zdiff3 — show base in conflict markers\u001b[39m\u001b[K"] +[16.845243461,"o","\r\n\r"] +[16.845277922,"o","\n\r\n\r\u001b[7A\u001b["] +[16.845324808,"o","13C\u001b[?25h"] +[17.08745653,"o","\u001b[?25l\u001b[13D\r\n\r\n\r\n\r\n [ ] merge.conflictstyle = zdiff3 — show base in conflict markers\u001b[K\r\n\u001b[38;5;14m>\u001b[39m \u001b[38;5;14m[x]\u001b[39m \u001b[38;5;14mrerere.enabled = true — remember and reuse conflict resolutions [✓ already set]\u001b[39m\u001b[K\r\n\r\n\r\u001b[7A\u001b[13C\u001b[?25h"] +[17.339612333,"o","\u001b[?25l\u001b[13D\r\n\r\n\r\n\r"] +[17.33962685,"o","\n\r"] +[17.339628172,"o","\n "] +[17.339636708,"o"," \u001b["] +[17.339694294,"o","38;5;10m[x]\u001b[39m rerere.enabled = true — remember and reuse conflict resolutions [✓ already set]\u001b[K\r\n\u001b["] +[17.339731814,"o","38;5;14m>\u001b[39m \u001b["] +[17.339734098,"o","38;5;14m"] +[17.339753243,"o","[ ]"] +[17.339787413,"o","\u001b[39m \u001b[38;5;14mcore.pager = delta — beautiful syntax-highlighted diffs (requires cargo)\u001b[39m\u001b[K\r\n"] +[17.339834089,"o","\r\u001b[7A\u001b[13C\u001b[?25h"] +[17.941085616,"o","\u001b[?25l\u001b[13D\r\n\r\n\r\n\r\n\r\n\r"] +[17.941092308,"o","\n\u001b[38;5;14"] +[17.941094612,"o","m"] +[17.941107286,"o",">\u001b[39m"] +[17.9411094,"o"," \u001b["] +[17.94112021,"o","38;5;14m[x]\u001b["] +[17.941132542,"o","39m \u001b[38;5;14"] +[17.941141629,"o","mcore.pager = delta — beautiful syntax-highlighted diffs (requires cargo)\u001b[39"] +[17.941157178,"o","m\u001b[K\r\n"] +[17.941168579,"o","\r"] +[17.941179679,"o","\u001b[7A\u001b["] +[17.941181252,"o","13C\u001b[?25h"] +[18.142866927,"o","\u001b[?25l\u001b[13D\u001b[38;5;10m>\u001b[39"] +[18.142888567,"o","m Git config"] +[18.142890761,"o"," "] +[18.142892615,"o","\u001b["] +[18.142904998,"o","38;5;14m"] +[18.142915006,"o","push.autoSetupRemote = true — auto-set upstream on first push, help.autocorrect = prompt — suggest corrections for mistyped com"] +[18.14291696,"o","\u001b[39"] +[18.142918573,"o","m"] +[18.142926096,"o","\u001b[K\r"] +[18.14292793,"o","\n\u001b["] +[18.142947686,"o","38;"] +[18.142969447,"o","5;14mmands [✓ already set], diff.algorithm = histogram — cleaner diffs for moved code [✓ already set], rerere.enabled = true — remember and reuse\u001b[39"] +[18.143022555,"o","m\u001b[K\r\n\u001b[38;5;14m conflict resolutions [✓ already set], core.pager = delta — beautiful syntax-highlighted diffs (requires cargo)\u001b[39m\u001b[K\r\n"] +[18.143037572,"o","\u001b[2K\r\n\u001b[2K\r"] +[18.143039746,"o","\n\u001b[2K\r\n"] +[18.143050897,"o","\u001b[2K\r"] +[18.143062067,"o","\n"] +[18.143085461,"o","\u001b[2K\r\n\u001b[?25h"] +[18.143150709,"o","\u001b[5A\u001b[?25h\r\n Summary:\r\n ◆ hooks: conventional-commits, branch-naming\r\n ◆ .gitignore: agentic\r\n"] +[18.143154296,"o"," ◆ .gitattributes: line-endings\r\n ◆ git config: push.autoSetupRemote, help.autocorrect, diff.algorithm, rerere.enabled, core.pager\r\n\r\n"] +[18.143217412,"o","\u001b[?25l\u001b[38;5;10m?\u001b[39"] +[18.143257757,"o","m Apply these changes? (Y/n) \r\u001b[29C"] +[18.143272053,"o","\u001b[?25h"] +[19.792232675,"o","\u001b[?25l\u001b[29D\u001b[38;5;10m?\u001b[39m Apply these changes? (Y/n) y "] +[19.792238877,"o","\u001b[K\r\u001b["] +[19.792253253,"o","30C\u001b[?25h"] +[19.994076103,"o","\u001b[?25l\u001b[30D\u001b[38;5;10m>\u001b[39m Apply these changes? \u001b["] +[19.994083246,"o","38;5;14mYes\u001b["] +[19.994094867,"o","39m\u001b[K\r\n\u001b[?25h"] +[19.994151462,"o","\u001b[?25h\r\n"] +[19.994267556,"o"," ◇ hook 'conventional-commits' installed ✓\r\n"] +[19.994332987,"o"," ◇ hook 'branch-naming' installed ✓\r\n"] +[19.994456638,"o"," ◇ .gitignore updated ✓\r\n"] +[19.994536174,"o"," ◇ .gitattributes applied ✓\r\n"] +[20.017942778,"o"," ◇ git config applied ✓\r\n\r\n"] +[20.018006045,"o","\u001b[?25l\u001b[38;5;10m?"] +[20.018027314,"o","\u001b[39m Save this configuration as a reusable build? "] +[20.018071277,"o","(y/N) \r\u001b[53C\u001b[?25h"] +[21.652036013,"o","\u001b[?25l\u001b[53D\u001b[38;5;10m?\u001b[39m Save this configuration as a reusable build? (y/N) n \u001b[K\r\u001b[54"] +[21.652041623,"o","C\u001b[?25h"] +[21.853730717,"o","\u001b[?25l\u001b[54D\u001b[38;5;10m>\u001b[39m Save this configuration as a reusable build? \u001b[38;5;14mNo\u001b[39m\u001b[K\r\n\u001b[?25h"] +[21.853780719,"o","\u001b[?25h\r\n Done\r\n\r\n"] +[21.854873675,"o","\u001b]697;OSCUnlock=e7d40ddbbcf646adb2a3f991e2653752\u0007\u001b]697;Dir=/home/jheisonmblivecom\u0007"] +[21.854924569,"o","\u001b]697;Shell=bash\u0007\u001b]697;ShellPath=/usr/bin/bash\u0007"] +[21.854980031,"o","\u001b]697;WSLDistro=Ubuntu\u0007\u001b]697;PID=384284\u0007"] +[21.855039231,"o","\u001b]697;ExitCode=0\u0007\u001b]697;TTY=/dev/pts/3\u0007"] +[21.855096236,"o","\u001b]697;Log=\u0007\u001b]697;User=jheisonmblivecom\u0007"] +[21.856478418000002,"o","\u001b]697;OSCLock=e7d40ddbbcf646adb2a3f991e2653752\u0007\u001b]697;PreExec\u0007"] +[22.024984814,"o","\u001b[?2004h\u001b]697;StartPrompt\u0007\u001b[1;32mgitkit@univerlab\u001b[0m:\u001b[1;34m~\u001b[0m$ \u001b]697;EndPrompt\u0007\u001b]697;NewCmd=e7d40ddbbcf646adb2a3f991e2653752\u0007"] diff --git a/demo/demo.toml b/demo/demo.toml new file mode 100644 index 0000000..bc3a17d --- /dev/null +++ b/demo/demo.toml @@ -0,0 +1,224 @@ +[demo] +name = "demo" +output_dir = "./dist" +prompt = '\[\e[1;32m\]gitkit@univerlab\[\e[0m\]:\[\e[1;34m\]~\[\e[0m\]$ ' + +[typing] +base_ms = 80 +salt_ms = 15 + +[layout] +width = 1440 +height = 1080 +fps = 15 +line_height = 1.2000000476837158 +background = "#0b0f14" +font_family = "IBM Plex Mono" + +[[layout.panes]] +id = "main" +type = "terminal" +x = 0 +y = 0 +width = 1440 +height = 1080 +font_family = "monospace" +font_size = 16 + +[[timeline]] +action = "focus" +pane = "main" + +[[timeline]] +action = "type" +text = "gitkit clone https://github.com/Univerlab/gitkit.git" +human_salt = true + +[[timeline]] +action = "wait" +duration_ms = 200 + +[[timeline]] +action = "keypress" +key = "enter" + +[[timeline]] +action = "wait" +duration_ms = 200 + +[[timeline]] +action = "keypress" +key = "enter" + +[[timeline]] +action = "wait_for_quiet" +quiet_ms = 1500 + +[[timeline]] +action = "keypress" +key = "down" + +[[timeline]] +action = "wait" +duration_ms = 349 + +[[timeline]] +action = "keypress" +key = "down" + +[[timeline]] +action = "wait" +duration_ms = 1316 + +[[timeline]] +action = "type" +text = " " +human_salt = true + +[[timeline]] +action = "wait" +duration_ms = 2295 + +[[timeline]] +action = "keypress" +key = "down" + +[[timeline]] +action = "wait" +duration_ms = 2500 + +[[timeline]] +action = "type" +text = " " +human_salt = true + +[[timeline]] +action = "wait" +duration_ms = 200 + +[[timeline]] +action = "keypress" +key = "enter" + +[[timeline]] +action = "wait_for_quiet" +quiet_ms = 500 + +[[timeline]] +action = "type" +text = " " +human_salt = true + +[[timeline]] +action = "wait" +duration_ms = 200 + +[[timeline]] +action = "keypress" +key = "enter" + +[[timeline]] +action = "wait" +duration_ms = 200 + +[[timeline]] +action = "keypress" +key = "enter" + +[[timeline]] +action = "wait_for_quiet" +quiet_ms = 1500 + +[[timeline]] +action = "keypress" +key = "down" + +[[timeline]] +action = "wait" +duration_ms = 180 + +[[timeline]] +action = "keypress" +key = "down" + +[[timeline]] +action = "wait" +duration_ms = 180 + +[[timeline]] +action = "keypress" +key = "down" + +[[timeline]] +action = "wait" +duration_ms = 174 + +[[timeline]] +action = "keypress" +key = "down" + +[[timeline]] +action = "wait" +duration_ms = 183 + +[[timeline]] +action = "keypress" +key = "down" + +[[timeline]] +action = "wait" +duration_ms = 452 + +[[timeline]] +action = "type" +text = " " +human_salt = true + +[[timeline]] +action = "wait" +duration_ms = 200 + +[[timeline]] +action = "keypress" +key = "enter" + +[[timeline]] +action = "wait_for_quiet" +quiet_ms = 1500 + +[[timeline]] +action = "type" +text = "y" +human_salt = true + +[[timeline]] +action = "wait" +duration_ms = 200 + +[[timeline]] +action = "keypress" +key = "enter" + +[[timeline]] +action = "wait_for_quiet" +quiet_ms = 1500 + +[[timeline]] +action = "type" +text = "n" +human_salt = true + +[[timeline]] +action = "wait" +duration_ms = 200 + +[[timeline]] +action = "keypress" +key = "enter" + +[[timeline]] +action = "wait_for_quiet" +quiet_ms = 1500 + +[[timeline]] +action = "terminate" diff --git a/demo/dist/demo.gif b/demo/dist/demo.gif new file mode 100644 index 0000000..4327a73 Binary files /dev/null and b/demo/dist/demo.gif differ diff --git a/demo/dist/demo.mp4 b/demo/dist/demo.mp4 new file mode 100644 index 0000000..3e07c1e Binary files /dev/null and b/demo/dist/demo.mp4 differ diff --git a/docs/builds.md b/docs/builds.md new file mode 100644 index 0000000..c346c14 --- /dev/null +++ b/docs/builds.md @@ -0,0 +1,37 @@ +--- +title: Builds +description: Save a repo configuration once and re-apply it to any project. +order: 7 +--- + +# Builds + +A **build** is a saved snapshot of a repository's gitkit configuration — +hooks, ignore templates, attributes and config presets — stored as a TOML +file under `~/.gitkit/builds/`. + +Builds turn your preferred setup into a one-liner for every future +project. + +## Workflow + +```bash +# Configure a repo the way you like, then save it +gitkit build save rust-dev --description "Rust development setup" + +# Apply it to any other project +cd /path/to/other/project +gitkit build apply rust-dev +``` + +## Managing builds + +```bash +gitkit build list # list saved builds +gitkit build save # save current repo config as a build +gitkit build apply # apply a saved build +gitkit build delete # delete a saved build +``` + +Because builds are plain TOML files, you can version them in your dotfiles +and share them across machines. diff --git a/docs/cli-reference.md b/docs/cli-reference.md new file mode 100644 index 0000000..ac23f74 --- /dev/null +++ b/docs/cli-reference.md @@ -0,0 +1,77 @@ +--- +title: CLI Reference +description: Every gitkit command and flag. +order: 8 +--- + +# CLI Reference + +``` +gitkit [command] [options] +``` + +Running `gitkit` with no command starts the interactive wizard. + +## Core + +| Command | Description | +|---|---| +| `gitkit` / `gitkit init` | Interactive wizard to configure the repo | +| `gitkit status` | Show current configuration status | +| `gitkit clone [dir]` | Clone a repository and run the wizard | +| `gitkit clone -b ` | Clone a specific branch | + +## Hooks + +| Command | Description | +|---|---| +| `gitkit hooks add ` | Install a built-in hook (hook type inferred) | +| `gitkit hooks add ` | Install a custom shell command as a hook | +| `gitkit hooks list` | List installed hooks | +| `gitkit hooks list --available` | Show all built-in hooks with descriptions | +| `gitkit hooks remove ` | Remove an installed hook | +| `gitkit hooks show ` | Print hook content | + +## Ignore + +| Command | Description | +|---|---| +| `gitkit ignore add ` | Generate/merge `.gitignore` via gitignore.io | +| `gitkit ignore list [filter]` | List available templates | + +## Attributes + +| Command | Description | +|---|---| +| `gitkit attributes init` | Apply line-endings preset to `.gitattributes` | + +## Config + +| Command | Description | +|---|---| +| `gitkit config apply defaults` | `push.autoSetupRemote`, `help.autocorrect`, `diff.algorithm` | +| `gitkit config apply advanced` | `merge.conflictstyle zdiff3`, `rerere.enabled` | +| `gitkit config apply delta` | `core.pager delta` | +| `gitkit config show` | Show current git config values | + +Scope: `--global` (all repos) or `--local` (current repo). Default is +`--local` inside a repo, `--global` otherwise. + +## Builds + +| Command | Description | +|---|---| +| `gitkit build list` | List saved builds | +| `gitkit build save ` | Save current repo config as a build | +| `gitkit build apply ` | Apply a saved build | +| `gitkit build delete ` | Delete a saved build | + +## Global flags + +| Flag | Description | +|---|---| +| `--yes`, `-y` | Skip confirmation prompts | +| `--force`, `-f` | Overwrite existing files | +| `--dry-run` | Preview changes without applying | +| `--help` | Show help for any command | +| `--version` | Show gitkit version | diff --git a/docs/config-presets.md b/docs/config-presets.md new file mode 100644 index 0000000..501184d --- /dev/null +++ b/docs/config-presets.md @@ -0,0 +1,43 @@ +--- +title: Config Presets +description: Curated git config presets with scope control and idempotency detection. +order: 6 +--- + +# Config Presets + +Gitkit ships curated git config presets — small sets of options that make +day-to-day git noticeably better: + +| Preset | What it sets | +|---|---| +| `defaults` | `push.autoSetupRemote`, `help.autocorrect`, `diff.algorithm histogram` | +| `advanced` | `merge.conflictstyle zdiff3`, `rerere.enabled` | +| `delta` | `core.pager delta` (requires [delta](https://github.com/dandavison/delta)) | + +```bash +gitkit config apply defaults +gitkit config apply advanced +gitkit config show # current git config values +``` + +## Scope + +- `--global` — apply to global git config (all repos) +- `--local` — apply to the current repo only +- Default: `--local` if inside a repo, `--global` otherwise + +## Idempotency + +Options already set with the same value are detected and skipped: + +``` +$ gitkit config apply defaults --global +✓ push.autoSetupRemote = true (already set) +✓ help.autocorrect = prompt (already set) +✓ diff.algorithm = histogram (already set) + +All configs already applied. +``` + +Running a preset twice never duplicates or clobbers anything. diff --git a/docs/hooks.md b/docs/hooks.md new file mode 100644 index 0000000..19ea89e --- /dev/null +++ b/docs/hooks.md @@ -0,0 +1,41 @@ +--- +title: Hooks +description: Built-in hooks (conventional commits, secret detection, branch naming) and custom shell commands. +order: 4 +--- + +# Hooks + +## Built-in hooks + +Built-ins are embedded in the binary — no network required. + +| Name | Hook | Description | +|---|---|---| +| `conventional-commits` | `commit-msg` | Validates Conventional Commits format | +| `no-secrets` | `pre-commit` | Detects common secret patterns in staged changes | +| `branch-naming` | `pre-commit` | Validates branch name matches convention | + +```bash +gitkit hooks list --available # see all built-ins with descriptions +gitkit hooks add no-secrets # install one (hook type inferred) +``` + +## Custom hooks + +Wire any shell command into a git hook: + +```bash +gitkit hooks add pre-push "cargo test" +``` + +## Managing hooks + +```bash +gitkit hooks list # list installed hooks +gitkit hooks show # print hook content +gitkit hooks remove # remove an installed hook +``` + +The `gitkit` wizard also shows installed hooks, pre-selects them and +allows removal interactively. diff --git a/docs/ignore-and-attributes.md b/docs/ignore-and-attributes.md new file mode 100644 index 0000000..52e8266 --- /dev/null +++ b/docs/ignore-and-attributes.md @@ -0,0 +1,35 @@ +--- +title: Ignore & Attributes +description: Generate .gitignore from templates and apply .gitattributes presets. +order: 5 +--- + +# Ignore & Attributes + +## `.gitignore` + +Gitkit generates and merges `.gitignore` files from +[gitignore.io](https://www.toptal.com/developers/gitignore) templates plus +its own built-ins (like `agentic` for AI coding agents): + +```bash +gitkit ignore add rust,vscode,agentic # generate/merge templates +gitkit ignore list # list available templates +gitkit ignore list python # filter the list +``` + +Existing patterns are merged, not overwritten — your manual entries +survive. + +## `.gitattributes` + +```bash +gitkit attributes init +``` + +Applies a line-endings preset (`* text=auto eol=lf`) and binary-file +attributes so checkouts behave identically across Linux, macOS and +Windows. + +Both files can also be configured interactively from the `gitkit` wizard, +which offers a filterable search across all templates. diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..b23e971 --- /dev/null +++ b/docs/index.md @@ -0,0 +1,52 @@ +--- +title: Gitkit +description: Set up a git repo the way you actually work — hooks, .gitignore, .gitattributes and git config in one guided flow. +order: 1 +--- + +# Gitkit + +Gitkit sets up a git repository the way you actually work: one guided flow +for hooks, `.gitignore`, `.gitattributes` and git config. It is a single +Rust binary — no Node.js, no Python, no runtime dependencies. + +## Why gitkit + +Every new repository needs the same ritual: pick a `.gitignore`, normalize +line endings, install a commit-message hook, set the git config options +you always set. Doing it by hand is error-prone; doing it with four +different tools (husky, gitignore.io, dotfiles, …) drags in runtimes and +copy-paste. Gitkit folds the whole ritual into one interactive wizard — +and lets you **save the result as a build** you can re-apply to any +project with one command. + +- **Guided repo setup** — `gitkit` (no arguments) walks you through + everything, showing what is already configured. +- **Status overview** — `gitkit status` shows hooks, ignore patterns, + attributes and config at a glance. +- **Clone and bootstrap** — `gitkit clone ` clones and drops straight + into the wizard. +- **Hook management** — built-in hooks (conventional commits, secret + detection, branch naming) or your own shell command. +- **Ignore & attribute presets** — all gitignore.io templates plus + built-ins, line-ending and binary presets. +- **Curated git config** — practical presets with `--global`/`--local` + scope and idempotency detection. +- **Builds** — save a configuration once, apply it everywhere. + +## How the documentation is organized + +- [Installation](installation.md) — install, update and uninstall. +- [Quick Start](quickstart.md) — the wizard and the one-liner workflow. +- [Hooks](hooks.md) — built-in and custom hooks. +- [Ignore & Attributes](ignore-and-attributes.md) — `.gitignore` and `.gitattributes`. +- [Config Presets](config-presets.md) — curated git config, scopes, idempotency. +- [Builds](builds.md) — save and reuse configurations. +- [CLI Reference](cli-reference.md) — every command and flag. + +## Part of UniverLab + +Gitkit is an experiment of [UniverLab](https://github.com/UniverLab), +an open computational laboratory. It follows the lab's engineering +principles: one tool one job, reproducibility first, offline-friendly +design. diff --git a/docs/installation.md b/docs/installation.md new file mode 100644 index 0000000..505bc4c --- /dev/null +++ b/docs/installation.md @@ -0,0 +1,50 @@ +--- +title: Installation +description: Install gitkit with the quick installer, cargo, or from GitHub Releases. +order: 2 +--- + +# Installation + +## Quick install (recommended) + +**Linux / macOS:** + +```bash +curl -fsSL https://raw.githubusercontent.com/UniverLab/gitkit/main/scripts/install.sh | sh +``` + +**Windows (PowerShell):** + +```powershell +irm https://raw.githubusercontent.com/UniverLab/gitkit/main/scripts/install.ps1 | iex +``` + +## Via cargo + +```bash +cargo install gitkit +``` + +Available on [crates.io](https://crates.io/crates/gitkit). + +## GitHub Releases + +Precompiled binaries for Linux x86_64, macOS x86_64/ARM64 and Windows +x86_64 are published on the +[Releases](https://github.com/UniverLab/gitkit/releases) page. + +## Uninstall + +**Linux / macOS:** + +```bash +rm -f ~/.local/bin/gitkit +rm -rf ~/.gitkit/ # saved builds (optional) +``` + +**Windows (PowerShell):** + +```powershell +Remove-Item "$env:LOCALAPPDATA\gitkit\gitkit.exe" -Force +``` diff --git a/docs/quickstart.md b/docs/quickstart.md new file mode 100644 index 0000000..a022725 --- /dev/null +++ b/docs/quickstart.md @@ -0,0 +1,76 @@ +--- +title: Quick Start +description: The wizard, the status overview, and clone-and-configure in one command. +order: 3 +--- + +# Quick Start + +## The wizard + +Run gitkit with no arguments inside (or outside) a repository: + +```bash +gitkit +# or explicitly: +gitkit init +``` + +The wizard guides you step by step and shows what is already configured: + +- **Hooks** — shows installed hooks, pre-selects them, allows removal. +- **`.gitignore`** — filterable search across all gitignore.io templates + plus built-ins. +- **`.gitattributes`** — line endings and binary file presets. +- **Git config** — shows current values, allows removal. +- **Custom hooks** — interactive picker for hook type selection. + +If the current directory is not a git repository, gitkit initializes one +automatically. + +## Clone and configure in one command + +```bash +gitkit clone https://github.com/user/repo # clone + wizard +gitkit clone -b develop https://github.com/user/repo # specific branch +gitkit clone https://github.com/user/repo my-project # custom directory +``` + +The wizard runs automatically after cloning. + +## Direct commands + +Everything the wizard does is also available as direct commands: + +```bash +gitkit hooks add conventional-commits +gitkit ignore add rust,vscode,agentic +gitkit attributes init +gitkit config apply defaults +``` + +## Check the result + +```bash +gitkit status +``` + +``` +Hooks: + ✓ conventional-commits (commit-msg) + ✓ custom: pre-push → "cargo test" + +.gitignore: + ✓ 14 patterns + +.gitattributes: + ✓ line-endings (eol=lf) + +Git config (global): + ✓ push.autoSetupRemote = true + ✓ help.autocorrect = prompt + ✓ diff.algorithm = histogram +``` + +When you are happy with a setup, [save it as a build](builds.md) and apply +it to every future project with one command. diff --git a/scripts/install.ps1 b/scripts/install.ps1 index 86ff107..3db2291 100644 --- a/scripts/install.ps1 +++ b/scripts/install.ps1 @@ -47,6 +47,26 @@ try { Fail "Download failed: $_`nURL: $Url" } +# --- verify checksum --- +# Match against SHA256SUMS.txt from the same release. Missing sums file (older +# releases) -> skip; a present-but-mismatched checksum is fatal. +$SumsUrl = "https://github.com/$Repo/releases/download/$Tag/SHA256SUMS.txt" +$SumsOk = $false +try { + Invoke-WebRequest -Uri $SumsUrl -OutFile "$Tmp\SHA256SUMS.txt" -UseBasicParsing + $SumsOk = $true +} catch { + Info "checksum" "SHA256SUMS.txt not found for $Tag - skipping verification" +} +if ($SumsOk) { + $line = Select-String -Path "$Tmp\SHA256SUMS.txt" -Pattern ([regex]::Escape($Archive)) | Select-Object -First 1 + if (-not $line) { Fail "No checksum listed for $Archive in SHA256SUMS.txt" } + $expected = ($line.Line -split '\s+')[0].ToLower() + $actual = (Get-FileHash "$Tmp\$Archive" -Algorithm SHA256).Hash.ToLower() + if ($actual -ne $expected) { Fail "Checksum mismatch for $Archive (expected $expected, got $actual)" } + Info "checksum" "verified" +} + # --- extract --- Expand-Archive -Path "$Tmp\$Archive" -DestinationPath $Tmp -Force $extracted = Join-Path $Tmp $Binary diff --git a/scripts/install.sh b/scripts/install.sh index 633af69..43df96f 100644 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -50,6 +50,29 @@ info "download" "$URL" HTTP_CODE=$(curl -fSL -w '%{http_code}' -o "$TMPDIR/$ARCHIVE" "$URL" 2>/dev/null) || true [ "$HTTP_CODE" = "200" ] || error "Download failed (HTTP $HTTP_CODE). Check that $TAG exists for $TARGET at:\n $URL" +# --- verify checksum --- +# Match against SHA256SUMS.txt from the same release. Missing sums file (older +# releases) or no sha256 tool → skip; a present-but-mismatched checksum is fatal. +SUMS_URL="https://github.com/$REPO/releases/download/${TAG}/SHA256SUMS.txt" +if curl -fsSL -o "$TMPDIR/SHA256SUMS.txt" "$SUMS_URL" 2>/dev/null; then + EXPECTED=$(awk -v f="$ARCHIVE" '$2 == f { print $1 }' "$TMPDIR/SHA256SUMS.txt" | head -1) + [ -n "$EXPECTED" ] || error "No checksum listed for $ARCHIVE in SHA256SUMS.txt" + if command -v sha256sum >/dev/null 2>&1; then + ACTUAL=$(sha256sum "$TMPDIR/$ARCHIVE" | awk '{ print $1 }') + elif command -v shasum >/dev/null 2>&1; then + ACTUAL=$(shasum -a 256 "$TMPDIR/$ARCHIVE" | awk '{ print $1 }') + else + ACTUAL="" + info "checksum" "no sha256 tool found — skipping verification" + fi + if [ -n "$ACTUAL" ]; then + [ "$ACTUAL" = "$EXPECTED" ] || error "Checksum mismatch for $ARCHIVE (expected $EXPECTED, got $ACTUAL)" + info "checksum" "verified" + fi +else + info "checksum" "SHA256SUMS.txt not found for $TAG — skipping verification" +fi + # --- extract --- tar xzf "$TMPDIR/$ARCHIVE" -C "$TMPDIR" [ -f "$TMPDIR/$BINARY" ] || error "Binary not found in archive" diff --git a/src/config/mod.rs b/src/config/mod.rs index feadd63..6221bbf 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -175,31 +175,36 @@ pub(crate) fn apply_config_keys( ) -> Result<()> { for key in keys { match *key { - "core.pager" => { - anyhow::ensure!( - cargo_available, - "cargo not found — cannot install git-delta" - ); - if !delta_installed() { - install_delta()?; - } - for (k, v) in DELTA_CONFIGS { - git_config_set(k, v, scope)?; - } - } - _ => { - let value = CONFIG_OPTIONS - .iter() - .find(|o| o.key == *key) - .and_then(|o| o.value) - .ok_or_else(|| anyhow::anyhow!("Unknown config key: {key}"))?; - git_config_set(key, value, scope)?; - } + "core.pager" => apply_pager_config(cargo_available, scope)?, + _ => apply_single_config(key, scope)?, } } Ok(()) } +fn apply_pager_config(cargo_available: bool, scope: ConfigScope) -> Result<()> { + anyhow::ensure!( + cargo_available, + "cargo not found — cannot install git-delta" + ); + if !delta_installed() { + install_delta()?; + } + for (k, v) in DELTA_CONFIGS { + git_config_set(k, v, scope)?; + } + Ok(()) +} + +fn apply_single_config(key: &str, scope: ConfigScope) -> Result<()> { + let value = CONFIG_OPTIONS + .iter() + .find(|o| o.key == key) + .and_then(|o| o.value) + .ok_or_else(|| anyhow::anyhow!("Unknown config key: {key}"))?; + git_config_set(key, value, scope) +} + type GitConfigs = &'static [(&'static str, &'static str)]; const DEFAULTS: GitConfigs = &[ diff --git a/src/init.rs b/src/init.rs index 00a685c..b22767c 100644 --- a/src/init.rs +++ b/src/init.rs @@ -329,24 +329,28 @@ fn load_ignore_templates() -> Vec { } fn get_installed_hooks() -> HashSet { - let mut installed = HashSet::new(); - if let Ok(root) = find_repo_root() { - let hooks_dir = root.join(".git").join("hooks"); - if hooks_dir.exists() { - if let Ok(entries) = fs::read_dir(&hooks_dir) { - for entry in entries.filter_map(|e| e.ok()) { - let name = entry.file_name().to_string_lossy().to_string(); - if !name.ends_with(".bak") && !name.ends_with(".sample") { - let content = fs::read_to_string(entry.path()).unwrap_or_default(); - if let Some(b) = hooks::detect_builtin(&name, &content) { - installed.insert(b.name.to_string()); - } - } - } - } - } + let Ok(root) = find_repo_root() else { + return HashSet::new(); + }; + let hooks_dir = root.join(".git").join("hooks"); + if !hooks_dir.exists() { + return HashSet::new(); } - installed + let Ok(entries) = fs::read_dir(&hooks_dir) else { + return HashSet::new(); + }; + entries + .filter_map(|e| e.ok()) + .filter(|e| { + let name = e.file_name().to_string_lossy().to_string(); + !name.ends_with(".bak") && !name.ends_with(".sample") + }) + .filter_map(|e| { + let name = e.file_name().to_string_lossy().to_string(); + let content = fs::read_to_string(e.path()).unwrap_or_default(); + hooks::detect_builtin(&name, &content).map(|b| b.name.to_string()) + }) + .collect() } fn get_configured_keys() -> HashSet { @@ -373,23 +377,22 @@ fn get_configured_keys() -> HashSet { } fn get_all_git_configs(scope: &str) -> std::collections::HashMap { - let mut configs = std::collections::HashMap::new(); - - if let Ok(output) = std::process::Command::new("git") + let Ok(output) = std::process::Command::new("git") .args(["config", scope, "--list"]) .output() - { - if output.status.success() { - let stdout = String::from_utf8_lossy(&output.stdout); - for line in stdout.lines() { - if let Some((key, value)) = line.split_once('=') { - configs.insert(key.to_string(), value.to_string()); - } - } - } + else { + return std::collections::HashMap::new(); + }; + if !output.status.success() { + return std::collections::HashMap::new(); } - - configs + String::from_utf8_lossy(&output.stdout) + .lines() + .filter_map(|line| { + line.split_once('=') + .map(|(k, v)| (k.to_string(), v.to_string())) + }) + .collect() } /// Maps selected display labels back to their corresponding keys.