mirror of
https://github.com/Xevion/dotfiles.git
synced 2026-01-31 00:24:06 -06:00
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
443 lines
13 KiB
Cheetah
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
|