Files
dotfiles/home/executable_dot_bash_aliases.tmpl
Xevion 8804b425fb feat: add git worktree management utilities for Fish and Bash
Add comprehensive worktree tooling with FZF integration:
- wtb: create branch worktree with gitignored file cloning
- wtcd/wtr: interactive picker and multi-remove with FZF
- wtf/wth: feature/hotfix branch shortcuts
- wts/wtl: status overview and listing
- Automatic .worktrees/ organization and .gitignore management
2026-01-23 13:42:30 -06:00

443 lines
13 KiB
Cheetah

# Controlled by chezmoi
# System
alias sctl='systemctl'
alias sctlu='systemctl --user'
alias jctl='journalctl'
alias sup='sudo apt update'
alias sug='sudo apt upgrade'
alias supg='sudo apt update && sudo apt upgrade'
alias supgy='sudo apt update && sudo apt upgrade -y'
alias sai='sudo apt install'
alias saa='sudo apt autoremove'
alias sdr='sudo systemctl daemon-reload'
alias sn='sudo micro'
alias sv='sudo nvim'
# Core aliases
alias ll='ls -AlFh'
alias la='ls -Ah'
alias l='ls -CF'
alias nano='micro'
alias lg='lazygit'
alias vim='nvim'
alias chlg='lazygit --path ~/.local/share/chezmoi'
alias es='exec $SHELL'
# Chezmoi
alias cha='chezmoi apply'
alias chai='chezmoi apply --interactive'
alias ch='chezmoi'
alias cdc='chezmoi cd'
# Remote Management
alias romanlog="ssh roman 'tail -F /var/log/syslog' --lines 100"
# Other aliases
alias oc='opencode'
alias ocs='opencode --model anthropic/claude-sonnet-4-5'
alias oco='opencode --model anthropic/claude-opus-4-5'
alias och='opencode --model anthropic/claude-haiku-4-5'
alias cl='claude'
alias cope='claude --model opus'
alias copes='claude --model sonnet'
alias copeh='claude --model haiku'
alias hcope='claude --model haiku'
alias gpt='chatgpt'
alias copilot='copilot'
alias suggest='copilot -p "suggest:" --allow-all-tools'
alias spt='spotify_player'
alias gitalias='alias | grep "git "'
alias mousefix='sudo udevadm trigger' # helped with mouse issues on laptop
alias rt='riptree'
alias tree='rt --compat'
# Clipboard aliases
{{ if not .wsl -}}
alias copy='xsel -ib'
alias paste='xsel -b'
alias cdp='cd $(xsel -b)'
{{- else -}}
alias copy='clip.exe'
alias paste='powershell.exe -noprofile Get-Clipboard'
alias cdp='cd $(xsel -b)'
{{- end }}
# fast chmod execute alias
function chfix() {
last_command=$(history | tail -n 1 | awk '{$1=""; sub(/^ /, ""); print $0}')
if [[ -f $last_command ]]; then
chmod +x $last_command
else
echo "Error: $last_command is not a valid file"
fi
}
# https://docs.gitignore.io/install/command-line
function gi() { curl -sL https://www.toptal.com/developers/gitignore/api/$@; }
function chcode() {
EDITOR="code --wait"
# If no arguments are provided, the chezmoi directory is opened
if [[ "$@" == *"--watch"* ]]; then
for arg in "$@"; do
if [[ ! $arg == -* ]]; then
chezmoi edit $@
return
fi
done
echo "--watch requires a file to be provided, directories aren't supported with watch mode"
fi
chezmoi edit $@
}
# Creates a temporary file with the given
function tempCode() {
if [ -z "$1" ]; then
echo "Must provide filetype argument (ex: py, .xml, html)"
else
# Remove preceding dot, then re-add to support both '.py' and 'py' as arguments
EXTENSION=$(echo $1 | sed 's/^\.//')
TEMP_FILE=$(mktemp "/tmp/XXXXXXXXXXXX_$(uuidgen).$EXTENSION")
echo "Temporary $1 file created at $TEMP_FILE"
code --file-uri "file://$TEMP_FILE"
fi
}
# Alias to disable/enable bluetooth connection to Galaxy Buds
budsAddress="60:3A:AF:75:61:80"
alias budsOff="bluetoothctl block $budsAddress"
alias budsOn="bluetoothctl unblock $budsAddress && bluetoothctl connect $budsAddress"
# Alias to disable/enable bluetooth connection to Bose QC45s
maestroAddress="AC:BF:71:66:FE:B2"
alias maestroOff="bluetoothctl block $maestroAddress"
alias maestroOn="bluetoothctl unblock $maestroAddress && bluetoothctl connect $maestroAddress"
function lastRuns() {
if [ -z "$1" ]; then RUNS=10; else RUNS=$1; fi
gh run list -L $RUNS --json name,url | jq -c '.[] | [.name, .url] | join(" ")' -r
}
# Touches a file while also creating the parent directory (and any other necessary directories) in order to do so.
mktouch() { mkdir -p $(dirname $1) && touch $1; }
# When in the appropriate KiTTy terminal, use the SSH kitten
[ "$TERM" = "xterm-kitty" ] && alias ssh="kitty +kitten ssh"
# Git aliases
alias ga='git add'
alias gaa='git add .'
alias gaaa='git add --all'
alias gau='git add --update'
alias gb='git branch'
alias gbd='git branch --delete '
alias gc='git commit'
alias gcm='git commit --message'
alias gcf='git commit --fixup'
alias gco='git checkout'
alias gcob='git checkout -b'
alias gcom='git checkout master'
alias gcos='git checkout staging'
alias gcod='git checkout develop'
alias gd='git diff'
alias gda='git diff HEAD'
# alias gi='git init'
alias glg='git log --graph --oneline --decorate --all'
alias gld='git log --pretty=format:"%h %ad %s" --date=short --all'
alias gm='git merge --no-ff'
alias gma='git merge --abort'
alias gmc='git merge --continue'
alias gp='git pull'
alias gpr='git pull --rebase'
alias gr='git rebase'
alias gs='git status'
alias gss='git status --short'
alias gst='git stash'
alias gsta='git stash apply'
alias gstd='git stash drop'
alias gstl='git stash list'
alias gstp='git stash pop'
alias gsts='git stash save'
# Git log find by commit message
function glf() { git log --all --grep="$1"; }
# =============================================================================
# Git Worktree Functions
# =============================================================================
# Helper: Sanitize branch name for directory (feature/foo -> feature-foo)
_wt_sanitize() {
echo "$1" | tr '/' '-'
}
# Helper: Get the current branch's tip (not HEAD if detached)
_wt_get_base() {
local branch
branch=$(git symbolic-ref --short HEAD 2>/dev/null)
if [[ -n "$branch" ]]; then
echo "$branch"
else
# Detached HEAD - try to find which branch we're on
git rev-parse HEAD
fi
}
# Helper: Get git repo root
_wt_root() {
git rev-parse --show-toplevel 2>/dev/null
}
# Helper: Clone gitignored files from source to destination worktree
_wt_clone_ignored() {
local src_path="$1"
local dst_path="$2"
# Find gitignored files (excluding .git and .worktrees)
local ignored_files
ignored_files=$(git -C "$src_path" ls-files --others --ignored --exclude-standard 2>/dev/null | \
grep -v "^\.git" | grep -v "^\.worktrees")
if [[ -z "$ignored_files" ]]; then
return 0
fi
echo ""
echo "Found gitignored files to copy..."
# Measure size with timeout
local size_output
local size_bytes=0
local size_human="unknown"
if size_output=$(timeout 10s du -sb "$src_path" --exclude='.git' --exclude='.worktrees' 2>/dev/null | head -n1); then
size_bytes=$(echo "$size_output" | awk '{print $1}')
size_human=$(numfmt --to=iec --suffix=B "$size_bytes" 2>/dev/null || echo "$size_bytes bytes")
fi
local threshold=$((100 * 1024 * 1024)) # 100MB
if [[ "$size_bytes" -gt "$threshold" ]] || [[ "$size_human" == "unknown" ]]; then
echo "Gitignored files size: $size_human"
read -p "Copy these files to new worktree? [y/N] " -n 1 -r
echo ""
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
echo "Skipping gitignored file copy"
return 0
fi
fi
echo "Copying gitignored files..."
echo "$ignored_files" | while read -r file; do
local src="$src_path/$file"
local dst="$dst_path/$file"
if [[ -e "$src" ]]; then
mkdir -p "$(dirname "$dst")"
cp -r "$src" "$dst" 2>/dev/null
fi
done
echo "Done copying gitignored files"
}
# wtl - List all worktrees
function wtl() {
git worktree list "$@"
}
# wts - Show git status for all worktrees
function wts() {
local root
root=$(_wt_root)
if [[ -z "$root" ]]; then
echo "Not in a git repository" >&2
return 1
fi
git worktree list --porcelain | grep '^worktree ' | cut -d' ' -f2- | while read -r wt_path; do
local branch
branch=$(git -C "$wt_path" symbolic-ref --short HEAD 2>/dev/null || echo "detached")
# Print header
echo -e "\033[1;34m╭─────────────────────────────────────────────────────────────╮\033[0m"
echo -e "\033[1;34m│\033[0m \033[1;33m$wt_path\033[0m"
echo -e "\033[1;34m│\033[0m \033[0;36m[$branch]\033[0m"
echo -e "\033[1;34m╰─────────────────────────────────────────────────────────────╯\033[0m"
# Run git status
git -C "$wt_path" status --short
echo ""
done
}
# wtr - FZF-based worktree remover (multi-select)
function wtr() {
local root
root=$(_wt_root)
if [[ -z "$root" ]]; then
echo "Not in a git repository" >&2
return 1
fi
# Get worktrees excluding the main one
local main_wt
main_wt=$(git worktree list --porcelain | grep '^worktree ' | head -n1 | cut -d' ' -f2-)
local selected
selected=$(git worktree list | grep -v "^$main_wt " | fzf --multi --height=40% --reverse \
--header="Select worktrees to remove (Tab to multi-select)" \
--preview="git -C {1} log --oneline -5 2>/dev/null || echo 'No commits'")
if [[ -z "$selected" ]]; then
echo "No worktrees selected"
return 0
fi
echo "Will remove the following worktrees:"
echo "$selected" | awk '{print " - " $1}'
echo ""
read -p "Confirm removal? [y/N] " -n 1 -r
echo ""
if [[ $REPLY =~ ^[Yy]$ ]]; then
echo "$selected" | while read -r line; do
local wt_path
wt_path=$(echo "$line" | awk '{print $1}')
echo "Removing $wt_path..."
git worktree remove "$wt_path"
done
else
echo "Cancelled"
fi
}
# wtb - Add worktree with new branch
# Usage: wtb <branch-name> [base-ref]
function wtb() {
if [[ -z "$1" ]]; then
echo "Usage: wtb <branch-name> [base-ref]" >&2
return 1
fi
local root
root=$(_wt_root)
if [[ -z "$root" ]]; then
echo "Not in a git repository" >&2
return 1
fi
local branch="$1"
local base="${2:-$(_wt_get_base)}"
local dir_name
dir_name=$(_wt_sanitize "$branch")
local wt_path="$root/.worktrees/$dir_name"
# Create .worktrees directory if needed
mkdir -p "$root/.worktrees"
# Add to .gitignore if not already there
if ! grep -q "^\.worktrees/?$" "$root/.gitignore" 2>/dev/null; then
echo ".worktrees/" >> "$root/.gitignore"
echo "Added .worktrees/ to .gitignore"
fi
echo "Creating worktree at $wt_path"
echo " Branch: $branch"
echo " Base: $base"
if git worktree add -b "$branch" "$wt_path" "$base"; then
echo ""
echo "Worktree created: $wt_path"
# Clone gitignored files from current worktree
local current_wt
current_wt=$(pwd)
_wt_clone_ignored "$current_wt" "$wt_path"
cd "$wt_path"
fi
}
# wtf / wtfeature - Create feature branch worktree
# Usage: wtf <name> [base-ref]
function wtf() {
if [[ -z "$1" ]]; then
echo "Usage: wtf <feature-name> [base-ref]" >&2
return 1
fi
wtb "feature/$1" "${2:-}"
}
function wtfeature() { wtf "$@"; }
# wth / wtfix - Create hotfix branch worktree
# Usage: wth <name> [base-ref]
function wth() {
if [[ -z "$1" ]]; then
echo "Usage: wth <hotfix-name> [base-ref]" >&2
return 1
fi
wtb "hotfix/$1" "${2:-}"
}
function wtfix() { wth "$@"; }
# wtcd - FZF-based worktree picker - cd into selected
function wtcd() {
local root
root=$(_wt_root)
if [[ -z "$root" ]]; then
echo "Not in a git repository" >&2
return 1
fi
local selected
selected=$(git worktree list | fzf --height=40% --reverse \
--header="Select worktree" \
--preview="git -C {1} log --oneline -5 2>/dev/null; echo ''; git -C {1} status --short 2>/dev/null")
if [[ -n "$selected" ]]; then
local wt_path
wt_path=$(echo "$selected" | awk '{print $1}')
cd "$wt_path"
fi
}
# fzf abbreviation/alias search
if command -v fzf-abbr-search.ts &> /dev/null && command -v fzf &> /dev/null; then
fzf_search_abbr() {
local result key selected
result=$(fzf-abbr-search.ts | fzf \
--ansi \
--height=50% \
--reverse \
--delimiter=$'\t' \
--with-nth=4 \
--nth=1,2 \
--prompt='Aliases > ' \
--preview='echo {2}' \
--preview-window=up:3:wrap \
--expect='tab' \
--header='Enter: insert name | Tab: insert expansion')
if [ -n "$result" ]; then
# First line is key, second line is selected
key=$(echo "$result" | head -n1)
selected=$(echo "$result" | tail -n1)
if [ -n "$selected" ]; then
if [ "$key" = "tab" ]; then
# Insert expansion (field 2)
READLINE_LINE="${READLINE_LINE:0:$READLINE_POINT}$(echo "$selected" | cut -f2)${READLINE_LINE:$READLINE_POINT}"
else
# Insert name (field 1)
READLINE_LINE="${READLINE_LINE:0:$READLINE_POINT}$(echo "$selected" | cut -f1)${READLINE_LINE:$READLINE_POINT}"
fi
fi
fi
}
# Bind to Alt+A
bind -x '"\ea": fzf_search_abbr'
fi