diff --git a/home/programs/default.nix b/home/programs/default.nix index 7b94659..bfced3c 100644 --- a/home/programs/default.nix +++ b/home/programs/default.nix @@ -11,6 +11,7 @@ ./fish.nix ./fzf.nix ./gh.nix + ./git.nix ./hyfetch.nix ./just.nix ./lazygit.nix diff --git a/home/programs/git.nix b/home/programs/git.nix new file mode 100644 index 0000000..585b4c7 --- /dev/null +++ b/home/programs/git.nix @@ -0,0 +1,425 @@ +{ + pkgs, + lib, + config, + ... +}: +{ + home.packages = with pkgs; [ + gum + clipboard-jh + ]; + + # ██████╗ ██╗████████╗ + # ██╔════╝ ██║╚══██╔══╝ + # ██║ ███╗██║ ██║ + # ██║ ██║██║ ██║ + # ╚██████╔╝██║ ██║ + # ╚═════╝ ╚═╝ ╚═╝ + + programs.git = { + enable = true; + + userName = "punkfairie"; + userEmail = "marley@punkfairie.net"; + + signing = { + signByDefault = true; + key = null; + }; + + lfs.enable = true; + + ignores = [ + "tags*" + "gems.tags" + ]; + + attributes = [ + # Auto-normalize line endings. + "* text=auto" + + "*.png binary" + "*.jpg binary" + "*.jpeg binary" + "*.bmp binary" + ]; + + # ██████╗ ███████╗██╗ ████████╗ █████╗ + # ██╔══██╗██╔════╝██║ ╚══██╔══╝██╔══██╗ + # ██║ ██║█████╗ ██║ ██║ ███████║ + # ██║ ██║██╔══╝ ██║ ██║ ██╔══██║ + # ██████╔╝███████╗███████╗██║ ██║ ██║ + # ╚═════╝ ╚══════╝╚══════╝╚═╝ ╚═╝ ╚═╝ + + delta = { + enable = true; + + options = { + navigate = true; + # TODO: Add to rose-pine-nix + syntax-theme = "rose-pine"; + features = "mellow-barbet"; + true-color = "always"; + hyperlinks = true; + }; + }; + + # ██████╗ ██████╗ ███╗ ██╗███████╗██╗ ██████╗ + # ██╔════╝██╔═══██╗████╗ ██║██╔════╝██║██╔════╝ + # ██║ ██║ ██║██╔██╗ ██║█████╗ ██║██║ ███╗ + # ██║ ██║ ██║██║╚██╗██║██╔══╝ ██║██║ ██║ + # ╚██████╗╚██████╔╝██║ ╚████║██║ ██║╚██████╔╝ + # ╚═════╝ ╚═════╝ ╚═╝ ╚═══╝╚═╝ ╚═╝ ╚═════╝ + + extraConfig = { + core = { + # Whitespace types to treat as errors. + # blank-at-eol: looks for spaces at EOL. + # -blank-at-eof: allows blank lines at EOF. + # space-before-tab: looks for spaces immediately before tabs at the + # beginning of the line. + whitespace = "blank-at-eol,-blank-at-eof,space-before-tab"; + + # Prevent showing files whose names contain non-ASCII symbols as + # unversioned. + precomposeunicode = false; + + # Speed up commands involving untracked files such as `git status`. + untrackedCache = true; + }; + + # Do not guess the user's identity. + user.useConfigOnly = true; + + color.diff = "always"; + + pretty = { + lo = "tformat:%C(auto)%h%C(reset)%C(auto)%d%C(reset) %s %C(italic blue)%ad%C(reset) %C(241)%aN%C(reset)"; + lc = "format:%C(auto)%h%C(reset) %C(white)-%C(reset) %C(italic blue)%ad%C(reset) %C(italic cyan)(%ar)%C(reset)%C(auto)%d%C(reset)%n %C(white)⤷%C(reset) %s %C(241)- %aN <%aE>%C(reset)%n"; + lt = "format:%C(auto)%h%C(reset) %C(white)-%C(reset) %C(italic blue)%ad%C(reset) %C(italic cyan)(%ar)%C(reset)%C(auto)%d%C(reset)%n %C(white)⤷%C(reset) %s %C(241)- %aN <%aE>%C(reset)%n%w(0,7,7)%+(trailers:only,unfold)"; + lf = "format:%C(auto)%h%C(reset)%C(auto)%d%C(reset) %C(italic 239)[P: %p] [T: %t]%C(reset)%n%C(white)Author:%C(reset) %aN %C(241)<%aE>%C(reset)%n %C(italic blue)%ad%C(reset) %C(italic cyan)(%ar)%C(reset)%n%C(white)Commit:%C(reset) %cN %C(241)<%cE>%C(reset) %C(italic 239)[GPG: %G?% GK]%C(reset)%n %C(italic blue)%cd%C(reset) %C(italic cyan)(%cr)%C(reset)%w(0,4,4)%n%n%C(bold)%s%C(reset)%n%n%-b%n%n%-N%n"; + rlo = "tformat:%C(auto)%h%C(reset) %C(bold yellow)(%C(magenta)%gd%C(bold yellow))%C(reset)%C(auto)%d%C(reset) %gs %C(italic blue)%ad%C(reset) %C(241)%aN%C(reset)"; + rlc = "format:%C(auto)%h%C(reset) %C(white)-%C(reset) %C(italic blue)%ad%C(reset) %C(italic cyan)(%ar)%C(reset)%C(auto)%d%C(reset)%n %C(white)⤷%C(reset) %s %C(241)- %aN <%aE>%C(reset)%n %C(white)⤷%C(reset) %C(bold yellow)(%C(magenta)%gd%C(bold yellow))%C(reset) %gs %C(241)- %gN <%gE>%C(reset)%n"; + rlt = "format:%C(auto)%h%C(reset) %C(white)-%C(reset) %C(italic blue)%ad%C(reset) %C(italic cyan)(%ar)%C(reset)%C(auto)%d%C(reset)%n %C(white)⤷%C(reset) %s %C(241)- %aN <%aE>%C(reset)%n %C(white)⤷%C(reset) %C(bold yellow)(%C(magenta)%gd%C(bold yellow))%C(reset) %gs %C(241)- %gN <%gE>%C(reset)%n%w(0,7,7)%+(trailers:only,unfold)"; + rlf = "format:%C(auto)%h%C(reset) %C(bold yellow)(%C(magenta)%gd%C(bold yellow))%C(reset)%C(auto)%d%C(reset) %C(italic 239)[P: %p] [T: %t]%C(reset)%n%C(white)Author:%C(reset) %aN %C(241)<%aE>%C(reset)%n %C(italic blue)%ad%C(reset) %C(italic cyan)(%ar)%C(reset)%n%C(white)Commit:%C(reset) %cN %C(241)<%cE>%C(reset) %C(italic 239)[GPG: %G?% GK]%C(reset)%n %C(italic blue)%cd%C(reset) %C(italic cyan)(%cr)%C(reset)%n%C(white)Reflog:%C(reset) %gN %C(241)<%gE>%C(reset)%n %C(italic)%gs%C(reset)%w(0,4,4)%n%n%C(bold)%s%C(reset)%n%n%-b%n%n%-N%n"; + }; + + init.defaultBranch = "main"; + + status.showstash = true; + + push = { + autoSetupRemote = true; + + # Push tags automatically. + followTags = true; + }; + + rebase.autosquash = true; + + merge = { + # Use nvim for merging, specifically the view showing LOCAL, MERGED, + # REMOTE. + tool = "nvimdiff2"; + + # Include summaries of merged commits in merge commit messages. + log = true; + + # delta. + conflictstyle = "diff3"; + }; + + mergetool = { + keepBackup = false; + hideResolved = true; + }; + + diff = { + # Detect when files are copied as well as renamed. + renames = "copies"; + + # delta + colorMoved = "default"; + }; + + # Fix whitespace errors when applying a patch, as specified in + # core.whitespace. + apply.whitespace = "fix"; + + branch = { + # Show most recently changed branches first. + sort = "-committerdate"; + + autoSetupMerge = "simple"; + }; + + credential.helper = "store"; + }; + + # █████╗ ██╗ ██╗ █████╗ ███████╗███████╗███████╗ + # ██╔══██╗██║ ██║██╔══██╗██╔════╝██╔════╝██╔════╝ + # ███████║██║ ██║███████║███████╗█████╗ ███████╗ + # ██╔══██║██║ ██║██╔══██║╚════██║██╔══╝ ╚════██║ + # ██║ ██║███████╗██║██║ ██║███████║███████╗███████║ + # ╚═╝ ╚═╝╚══════╝╚═╝╚═╝ ╚═╝╚══════╝╚══════╝╚══════╝ + + aliases = + let + fish_fns = config.programs.fish.functions; + in + { + ### Staging ### + + a = "add"; + aa = "add --all"; + + # Interactively stage parts of a file. + apa = "add --patch"; + + da = "diff"; + das = "diff --staged"; + daw = "diff --word-diff"; # Show diff by word. + dasw = "diff --staged --word-diff"; + + d = "!f() { git diff \"$@\" ':(exclude)package-lock.json' ':(exclude)*.lock'; }; f"; + ds = "!f() { git diff --staged \"$@\" ':(exclude)package-lock.json' ':(exclude)*.lock'; }; f"; + dw = "!f() { git diff --word-diff \"$@\" ':(exclude)package-lock.json' ':(exclude)*.lock'; }; f"; + dsw = "!f() { git diff --staged --word-diff \"$@\" ':(exclude)package-lock.json' ':(exclude)*.lock'; }; f"; + + st = "status --short --branch"; + stu = "status --short --branch --untracked-files"; + stl = "status"; + + ### Committing ### + + c = "commit"; + ce = "commit --amend"; + cen = "commit --amend --no-edit --no-verify"; + ca = "!git add --alll && git commit"; + cae = "!git add --all && git commit --amend"; + caen = "!git add --all && git commit --amend --no-edit --no-verify"; + cfu = "commit --fixup"; + + rev = "revert"; + + ### Working Dir & Index Manipulation ### + + co = "checkout"; + + rt = "reset"; + + rts = "reset --soft"; # undo commits & stage their changes + + rs = "restore --worktree"; # revert local changes + rst = "restore --staged"; # unstage things + rsa = "restore --worktree --staged"; + + rss = "restore --worktree --source"; # specify a commit to revert to + rsts = "restore --staged --source"; + rsas = "restore --worktree --staged --source"; + + rmc = "rm --cached"; # leave worktree copy alone + + sta = "stash push"; + stam = "stash push --message"; + staa = "stash push --include-untracted"; + staam = "stash push --include-untracted --message"; + + stap = "stash pop"; + stal = "stash list"; + stas = "stash show --text"; + + cl = "clean -force"; # remove untracked & unignored files + cldr = "clean --dry-run"; + + ### Branches ### + + b = "branch"; + cb = "checkout -b"; + cm = lib.mkIf (fish_fns ? git_main_branch) "!git checkout $(git_main_branch)"; + cd = lib.mkIf (fish_fns ? git_develop_branch) "!git checkout $(git_develop_branch)"; + + m = "merge"; + mtl = "mergetool --no-prompt"; + ma = "merge --abort"; + + cp = "cherry-pick"; + cpa = "cherry-pick --abort"; + cpc = "cherry-pick --continue"; + cpq = "cherry-pick --quit"; + + ### Remotes ### + + p = "push"; + pv = "push --verbose"; + pdr = "push --dry-run"; + pf = "push --force-with-lease --force-if-includes"; + pfv = "push --verbose --force-with-lease --force-if-includes"; + pff = "push --force"; + pffv = "push --verbose --force"; + + f = "fetch"; + fa = "fetch --all --prune"; + + pl = "pull"; + plr = "pull --rebase"; + + sub = "submodule"; + subu = "submodule update --init --recursive"; + + r = "remote"; + rv = "remote --verbose"; + ra = "remote add"; + rrm = "remote remove"; + rmv = "remote rename"; + rset = "remote set-url"; + rup = "remote update"; + + ### Logs ### + + # Current branch. + l = "log --pretty=lc --graph"; + lo = "log --pretty=lo --graph --date=human"; + ls = "log --pretty=lo --graph --date=human --simplify-by-decoration"; + lf = "log --pretty=lf --graph"; + ld = "log --pretty=lf --graph --cc --stat"; + lp = "log --pretty=lf --graph --cc --patch"; + + lr = "log -5 --pretty=lc --graph"; + lro = "log -5 --pretty=lo --graph --date=human"; + lrs = "log -5 --pretty=lo --graph --date=human --simplify-by-decoration"; + lrf = "log -5 --pretty=lf --graph"; + lrd = "log -5 --pretty=lf --graph --cc --stat"; + lrp = "log -5 --pretty=lf --graph --cc --patch"; + + # All branches on all remotes. + la = "log --pretty=lc --graph --all"; + lao = "log --pretty=lo --graph --all --date=human"; + las = "log --pretty=lo --graph --all --date=human --simplify-by-decoration"; + laf = "log --pretty=lf --graph --all"; + lad = "log --pretty=lf --graph --all --cc --stat"; + lap = "log --pretty=lf --graph --all --cc --patch"; + + lar = "log -5 --pretty=lc --graph --all"; + laro = "log -5 --pretty=lo --graph --all --date=human"; + lars = "log -5 --pretty=lo --graph --all --date=human --simplify-by-decoration"; + larf = "log -5 --pretty=lf --graph --all"; + lard = "log -5 --pretty=lf --graph --all --cc --stat"; + larp = "log -5 --pretty=lf --graph --all --cc --patch"; + + ### Shortcuts ### + + nevermind = "!git reset --hard head && git clean -df"; + + open = lib.mkIf (config.programs.fish.enable && (fish_fns ? git_open)) "!fish -c git_open"; + + chash = "!git log --oneline | gum filter --height 10 | cut -d' ' -f1 | cb &>/dev/null"; + }; + }; + + # ███╗ ███╗██╗███████╗ ██████╗ + # ████╗ ████║██║██╔════╝██╔════╝ + # ██╔████╔██║██║███████╗██║ + # ██║╚██╔╝██║██║╚════██║██║ + # ██║ ╚═╝ ██║██║███████║╚██████╗ + # ╚═╝ ╚═╝╚═╝╚══════╝ ╚═════╝ + + home.shellAbbrs = { + g = "git"; + }; + + programs.fish.functions = lib.mkIf config.programs.fish.enable { + git_develop_branch = # fish + '' + command git rev-parse --git-dir &>/dev/null || return + set -l branch + + for branch in dev devel develop development + if command git show-ref -q --verify refs/heads/$branch + echo $branch + return 0 + end + end + + echo develop + return 1 + ''; + + git_main_branch = # fish + '' + command git rev-parse --git-dir &>/dev/null || return + set -l ref + + for ref in + refs/{heads,remotes/{origin,upstream}}/{main,trunk,mainline,default,master} + if command git show-ref -q --verify $ref + echo (basename $ref) + return 0 + end + end + + echo main + return 1 + ''; + + git_current_branch = # fish + '' + set -l ref (git symbolic-ref --quiet HEAD 2>/dev/null) + set -l ret $status + + if [ $ret -ne 0 ] + [ $ret -eq 128 ] && return # no git repo + set ref (git rev-parse --short HEAD 2>/dev/null) || return + end + + echo (string replace "refs/heads/" "" $ref) + ''; + + git_open = # fish + '' + set -f origin "$(git remote get-url origin)" + + switch "$origin" + case "*github*" + if command -v gh &>/dev/null + gh repo view --web &>/dev/null + else + set_color red + echo "[ERROR] Install gh-cli to open Github repos." >&2 + set_color normal + return 1 + end + case "https://*" + open '$origin' >/dev/null & + case "*" + set_color red + printf "[ERROR] Unrecognized origin %s" "$origin" >&2 + return 1 + end + ''; + }; +} + +# ███╗ ██╗ ██████╗ ████████╗███████╗███████╗ +# ████╗ ██║██╔═══██╗╚══██╔══╝██╔════╝██╔════╝ +# ██╔██╗ ██║██║ ██║ ██║ █████╗ ███████╗ +# ██║╚██╗██║██║ ██║ ██║ ██╔══╝ ╚════██║ +# ██║ ╚████║╚██████╔╝ ██║ ███████╗███████║ +# ╚═╝ ╚═══╝ ╚═════╝ ╚═╝ ╚══════╝╚══════╝ + +# PATH VALUES: +# :/ - all files within the root of the working tree, so all files in the +# repo, a la git add's --all option. + +# A note on reset, restore, and revert, paraphrased from `man git`, plus my +# own on `rm`: +# +# revert - make a new commit that reverts a previous commit. +# restore - undo uncommitted changes in the working tree. +# reset - update the branch and change commit history. +# rm - same as system rm; remove files, either from git's knowledge, the +# working directory, or both. +# +# These are not a description of the *possible* uses, rather a narrowing of +# the *intended* uses.