update
This commit is contained in:
@@ -3,13 +3,25 @@
|
||||
set -euo pipefail
|
||||
|
||||
usage() {
|
||||
echo "Usage: $(basename "$0") -c COMMENT_SYMBOL [-e EXCLUDE_PATTERN]... TARGET"
|
||||
echo " -c Comment symbol (e.g., '#' or '//')"
|
||||
echo "Usage: $(basename "$0") <command> [options] TARGET"
|
||||
echo ""
|
||||
echo "Commands:"
|
||||
echo " add Add/update path comment in first 5 lines of each file"
|
||||
echo " undo Remove any path comment from first 5 lines of each file"
|
||||
echo ""
|
||||
echo "Options:"
|
||||
echo " -c Comment symbol (e.g., '#' or '//') [required for add]"
|
||||
echo " -e Exclude pattern (can be specified multiple times)"
|
||||
echo " TARGET File or directory to process"
|
||||
exit 1
|
||||
}
|
||||
|
||||
[[ $# -lt 1 ]] && usage
|
||||
|
||||
cmd="$1"
|
||||
shift
|
||||
|
||||
[[ "$cmd" != "add" && "$cmd" != "undo" ]] && usage
|
||||
|
||||
comment_sym=""
|
||||
excludes=()
|
||||
|
||||
@@ -24,66 +36,63 @@ done
|
||||
|
||||
shift $((OPTIND - 1))
|
||||
[[ $# -ne 1 ]] && usage
|
||||
[[ -z "$comment_sym" ]] && usage
|
||||
|
||||
if [[ "$cmd" == "add" ]]; then
|
||||
[[ -z "$comment_sym" ]] && {
|
||||
echo "Error: -c is required for add" >&2
|
||||
usage
|
||||
}
|
||||
fi
|
||||
|
||||
target="$(realpath "$1")"
|
||||
base_dir="$(pwd)"
|
||||
|
||||
strip_path_comment() {
|
||||
local file="$1"
|
||||
awk 'NR <= 5 && /path: / { next } { print }' "$file" >"$file.tmp"
|
||||
mv "$file.tmp" "$file"
|
||||
}
|
||||
|
||||
process_file() {
|
||||
local file="$1"
|
||||
# shellcheck disable=SC2295
|
||||
local rel_path="${file#$base_dir/}"
|
||||
|
||||
local path_comment="$comment_sym path: $rel_path"
|
||||
|
||||
# Read first two lines
|
||||
local line1 line2
|
||||
IFS= read -r line1 <"$file" 2>/dev/null || line1=""
|
||||
IFS= read -r line2 < <(tail -n +2 "$file") 2>/dev/null || line2=""
|
||||
strip_path_comment "$file"
|
||||
|
||||
local line1
|
||||
IFS= read -r line1 <"$file" 2>/dev/null || line1=""
|
||||
|
||||
# Handle shebang case
|
||||
if [[ "$line1" =~ ^#! ]]; then
|
||||
if [[ "$line2" == *"path: "* ]]; then
|
||||
# Replace existing path comment on line 2
|
||||
{
|
||||
echo "$line1"
|
||||
echo "$path_comment"
|
||||
tail -n +3 "$file"
|
||||
} >"$file.tmp"
|
||||
else
|
||||
# Insert new path comment after shebang
|
||||
{
|
||||
echo "$line1"
|
||||
echo "$path_comment"
|
||||
tail -n +2 "$file"
|
||||
} >"$file.tmp"
|
||||
fi
|
||||
{
|
||||
echo "$line1"
|
||||
echo "$path_comment"
|
||||
tail -n +2 "$file"
|
||||
} >"$file.tmp"
|
||||
else
|
||||
if [[ "$line1" == *"path: "* ]]; then
|
||||
# Replace existing path comment on line 1
|
||||
{
|
||||
echo "$path_comment"
|
||||
tail -n +2 "$file"
|
||||
} >"$file.tmp"
|
||||
else
|
||||
# Insert new path comment at top
|
||||
{
|
||||
echo "$path_comment"
|
||||
cat "$file"
|
||||
} >"$file.tmp"
|
||||
fi
|
||||
{
|
||||
echo "$path_comment"
|
||||
cat "$file"
|
||||
} >"$file.tmp"
|
||||
fi
|
||||
|
||||
mv "$file.tmp" "$file"
|
||||
}
|
||||
|
||||
if [[ -f "$target" ]]; then
|
||||
process_file "$target"
|
||||
elif [[ -d "$target" ]]; then
|
||||
find_cmd=(find "$target")
|
||||
dispatch() {
|
||||
local file="$1"
|
||||
if [[ "$cmd" == "add" ]]; then
|
||||
process_file "$file"
|
||||
else
|
||||
strip_path_comment "$file"
|
||||
fi
|
||||
}
|
||||
|
||||
# Always exclude hidden files and directories
|
||||
find_cmd+=(\( -name ".*" -prune \))
|
||||
if [[ -f "$target" ]]; then
|
||||
dispatch "$target"
|
||||
elif [[ -d "$target" ]]; then
|
||||
find_cmd=(find "$target" \( -name ".*" -prune \))
|
||||
|
||||
if [[ ${#excludes[@]} -gt 0 ]]; then
|
||||
find_cmd+=(-o \()
|
||||
@@ -98,7 +107,7 @@ elif [[ -d "$target" ]]; then
|
||||
find_cmd+=(-o -type f -print0)
|
||||
|
||||
while IFS= read -r -d '' file; do
|
||||
process_file "$file"
|
||||
dispatch "$file"
|
||||
done < <("${find_cmd[@]}")
|
||||
else
|
||||
echo "Error: $target is not a file or directory" >&2
|
||||
|
||||
67
config/shared/bin/catfiles
Executable file
67
config/shared/bin/catfiles
Executable file
@@ -0,0 +1,67 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT="."
|
||||
declare -a SKIP_DIRS=()
|
||||
declare -a PATTERNS=()
|
||||
|
||||
usage() {
|
||||
cat <<EOF
|
||||
Usage: $0 [-r root_dir] [-s dir_to_skip]... -p pattern ...
|
||||
|
||||
Options:
|
||||
-r DIR Root directory (default: .)
|
||||
-s DIR Directory name to skip (can repeat)
|
||||
-p PATTERN File pattern (e.g. '*.md', '*.txt') (required, can repeat)
|
||||
|
||||
Example:
|
||||
$0 -p '*.md'
|
||||
$0 -r src -s node_modules -s .git -p '*.md' -p '*.txt'
|
||||
EOF
|
||||
exit 1
|
||||
}
|
||||
|
||||
while getopts ":r:s:p:" opt; do
|
||||
case "$opt" in
|
||||
r) ROOT="$OPTARG" ;;
|
||||
s) SKIP_DIRS+=("$OPTARG") ;;
|
||||
p) PATTERNS+=("$OPTARG") ;;
|
||||
*) usage ;;
|
||||
esac
|
||||
done
|
||||
|
||||
shift $((OPTIND - 1))
|
||||
|
||||
# Require at least one pattern
|
||||
(( ${#PATTERNS[@]} > 0 )) || usage
|
||||
|
||||
# Build prune expression
|
||||
PRUNE_EXPR=()
|
||||
if (( ${#SKIP_DIRS[@]} > 0 )); then
|
||||
PRUNE_EXPR+=( \( )
|
||||
for i in "${!SKIP_DIRS[@]}"; do
|
||||
PRUNE_EXPR+=( -type d -name "${SKIP_DIRS[$i]}" -prune )
|
||||
[[ $i -lt $((${#SKIP_DIRS[@]} - 1)) ]] && PRUNE_EXPR+=( -o )
|
||||
done
|
||||
PRUNE_EXPR+=( \) -o )
|
||||
fi
|
||||
|
||||
# Build pattern expression
|
||||
PATTERN_EXPR=( \( )
|
||||
for i in "${!PATTERNS[@]}"; do
|
||||
PATTERN_EXPR+=( -name "${PATTERNS[$i]}" )
|
||||
[[ $i -lt $((${#PATTERNS[@]} - 1)) ]] && PATTERN_EXPR+=( -o )
|
||||
done
|
||||
PATTERN_EXPR+=( \) )
|
||||
|
||||
# Execute
|
||||
find "$ROOT" \
|
||||
"${PRUNE_EXPR[@]}" \
|
||||
-type f \
|
||||
"${PATTERN_EXPR[@]}" \
|
||||
-print0 |
|
||||
while IFS= read -r -d '' file; do
|
||||
printf "\n===== %s =====\n" "$file"
|
||||
cat "$file"
|
||||
done
|
||||
|
||||
190
config/shared/bin/wt
Executable file
190
config/shared/bin/wt
Executable file
@@ -0,0 +1,190 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
########################################
|
||||
# Utilities
|
||||
########################################
|
||||
|
||||
fail() {
|
||||
echo "Error: $1" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
ensure_repo() {
|
||||
git rev-parse --is-inside-work-tree >/dev/null 2>&1 ||
|
||||
fail "Not inside a git repository"
|
||||
}
|
||||
|
||||
repo_root() {
|
||||
git rev-parse --show-toplevel
|
||||
}
|
||||
|
||||
detect_base() {
|
||||
if git show-ref --verify --quiet refs/heads/main; then
|
||||
echo "main"
|
||||
elif git show-ref --verify --quiet refs/heads/master; then
|
||||
echo "master"
|
||||
else
|
||||
git branch --show-current
|
||||
fi
|
||||
}
|
||||
|
||||
ensure_clean() {
|
||||
git diff --quiet || fail "Working tree is dirty"
|
||||
git diff --cached --quiet || fail "Index is dirty"
|
||||
}
|
||||
|
||||
########################################
|
||||
# Core Paths
|
||||
########################################
|
||||
|
||||
ROOT=$(repo_root)
|
||||
WT_DIR="$ROOT/.worktrees"
|
||||
|
||||
########################################
|
||||
# Commands
|
||||
########################################
|
||||
|
||||
cmd_new() {
|
||||
local name="$1"
|
||||
local base="${2:-$(detect_base)}"
|
||||
local path="$WT_DIR/$name"
|
||||
|
||||
[ -d "$path" ] && fail "Worktree directory already exists"
|
||||
git show-ref --verify --quiet "refs/heads/$name" &&
|
||||
fail "Branch '$name' already exists"
|
||||
|
||||
mkdir -p "$WT_DIR"
|
||||
|
||||
git worktree add -b "$name" "$path" "$base"
|
||||
|
||||
echo "Created worktree:"
|
||||
echo " branch: $name"
|
||||
echo " path: $path"
|
||||
}
|
||||
|
||||
cmd_list() {
|
||||
git worktree list
|
||||
}
|
||||
|
||||
cmd_path() {
|
||||
echo "$WT_DIR/$1"
|
||||
}
|
||||
|
||||
cmd_rm() {
|
||||
local name="$1"
|
||||
local path="$WT_DIR/$name"
|
||||
|
||||
[ -d "$path" ] || fail "Worktree not found"
|
||||
|
||||
git worktree remove "$path"
|
||||
git branch -D "$name" >/dev/null 2>&1 || true
|
||||
|
||||
echo "Removed worktree '$name'"
|
||||
}
|
||||
|
||||
cmd_finish() {
|
||||
local name="$1"
|
||||
shift
|
||||
local keep_worktree=0
|
||||
local path="$WT_DIR/$name"
|
||||
local base
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--keep-worktree)
|
||||
keep_worktree=1
|
||||
shift
|
||||
;;
|
||||
*)
|
||||
fail "Unknown option for finish: $1"
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
[ -d "$path" ] || fail "Worktree not found"
|
||||
git show-ref --verify --quiet "refs/heads/$name" ||
|
||||
fail "Branch '$name' not found"
|
||||
|
||||
base=$(detect_base)
|
||||
|
||||
echo "Base branch: $base"
|
||||
echo "Feature branch: $name"
|
||||
|
||||
git checkout "$base" >/dev/null
|
||||
ensure_clean
|
||||
|
||||
if ! git merge --squash "$name"; then
|
||||
echo
|
||||
echo "Resolve conflicts, then run:"
|
||||
echo " git commit"
|
||||
echo " wt rm $name"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
git commit -m "$name"
|
||||
|
||||
if [ -d "$path" ] && [ "$keep_worktree" -eq 0 ]; then
|
||||
if ! git worktree remove "$path"; then
|
||||
git worktree remove --force "$path" || fail "Failed to remove worktree '$name'"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "$keep_worktree" -eq 0 ]; then
|
||||
git branch -D "$name"
|
||||
fi
|
||||
|
||||
if [ "$keep_worktree" -eq 1 ]; then
|
||||
echo "Merged (squash) into '$base' and kept worktree '$path'"
|
||||
else
|
||||
echo "Merged (squash) and removed '$name'"
|
||||
fi
|
||||
}
|
||||
|
||||
cmd_prune() {
|
||||
git worktree prune
|
||||
echo "Pruned stale worktree metadata"
|
||||
}
|
||||
|
||||
########################################
|
||||
# Entry
|
||||
########################################
|
||||
|
||||
ensure_repo
|
||||
|
||||
case "${1:-}" in
|
||||
new)
|
||||
[ $# -lt 2 ] && fail "Usage: wt new <name> [base]"
|
||||
cmd_new "$2" "${3:-}"
|
||||
;;
|
||||
list)
|
||||
cmd_list
|
||||
;;
|
||||
path)
|
||||
[ $# -lt 2 ] && fail "Usage: wt path <name>"
|
||||
cmd_path "$2"
|
||||
;;
|
||||
finish)
|
||||
[ $# -lt 2 ] && fail "Usage: wt finish <name>"
|
||||
cmd_finish "$2" "${@:3}"
|
||||
;;
|
||||
rm)
|
||||
[ $# -lt 2 ] && fail "Usage: wt rm <name>"
|
||||
cmd_rm "$2"
|
||||
;;
|
||||
prune)
|
||||
cmd_prune
|
||||
;;
|
||||
*)
|
||||
cat <<EOF
|
||||
Usage:
|
||||
wt new <name> [base]
|
||||
wt list
|
||||
wt path <name>
|
||||
wt finish <name> [--keep-worktree]
|
||||
wt rm <name>
|
||||
wt prune
|
||||
EOF
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
@@ -21,4 +21,3 @@
|
||||
last = log -1 HEAD
|
||||
tags = tag -l
|
||||
undo = reset --mixed HEAD~1
|
||||
|
||||
|
||||
@@ -19,3 +19,6 @@ logs/
|
||||
coverage/
|
||||
.devflow/
|
||||
.dev-flow/
|
||||
.flow/
|
||||
.local/
|
||||
.worktrees/
|
||||
|
||||
Submodule config/shared/nvim updated: 6b4008ca38...e65a7f3f13
@@ -7,6 +7,9 @@ bind C-Space send-prefix
|
||||
# set -s default-terminal "tmux-256color"
|
||||
# set -sa terminal-overrides "$term:rgb"
|
||||
|
||||
set -g window-size largest
|
||||
set -g aggressive-resize on
|
||||
|
||||
set -s escape-time 10
|
||||
set -s focus-events on
|
||||
set -s set-clipboard on
|
||||
@@ -70,7 +73,7 @@ bind v split-window -h -c "#{pane_current_path}"
|
||||
# For some reason, choose-session is what I had before and it was working; however,
|
||||
# it does not appear in man page and does not allow customization
|
||||
# bind o choose-session
|
||||
bind o choose-tree -s -O name
|
||||
bind o choose-tree -Zs -O name
|
||||
bind N new-window
|
||||
bind n new-window -c "#{pane_current_path}"
|
||||
bind c confirm-before -p "kill-pane \#P? (y/n)" kill-pane
|
||||
@@ -82,5 +85,3 @@ unbind d
|
||||
bind e detach
|
||||
|
||||
bind d command-prompt -I "flow " 'run-shell "/home/tomas/bin/dev-tmux-wrapper.sh %1 --from #{session_name}"'
|
||||
|
||||
|
||||
|
||||
@@ -3,6 +3,8 @@ export LANG=en_US.UTF-8
|
||||
export LC_CTYPE=en_US.UTF-8
|
||||
export LC_COLLATE=C
|
||||
|
||||
export EDITOR=nvim
|
||||
|
||||
# eval "$(dircolors)"; echo "$LS_COLORS"
|
||||
export LS_COLORS='rs=0:di=01;34:ln=01;33:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:mi=00:su=37;41:sg=30;43:ca=00:tw=30;42:ow=34;42:st=37;44:ex=01;32'
|
||||
|
||||
@@ -13,6 +15,8 @@ SAVEHIST=10000
|
||||
setopt auto_cd interactive_comments prompt_subst share_history
|
||||
setopt append_history hist_ignore_dups hist_ignore_all_dups hist_reduce_blanks
|
||||
|
||||
fpath=(~/.zsh/completions $fpath)
|
||||
|
||||
autoload -Uz compinit
|
||||
zstyle ':completion:*' matcher-list 'm:{a-zA-Z}={A-Za-z}' # Case-insensitive
|
||||
zstyle ':completion:*' use-cache on
|
||||
@@ -76,10 +80,63 @@ alias ga='git add'
|
||||
alias gcm='git commit -m'
|
||||
alias gp='git push'
|
||||
alias gst='git status'
|
||||
alias gd='git diff --patience --color-moved=dimmed-zebra --word-diff=plain --function-context --ignore-space-change -U3'
|
||||
# alias gd='git diff --patience --color-moved=dimmed-zebra --word-diff=plain --function-context --ignore-space-change -U3'
|
||||
alias gd='git diff --patience --color-moved=dimmed-zebra --ignore-space-change -U5'
|
||||
alias glg='git log --oneline --graph --decorate --all'
|
||||
|
||||
alias k='kubectl'
|
||||
|
||||
# opencode
|
||||
export PATH=/home/tomas/.opencode/bin:$PATH
|
||||
export PATH="$HOME/.opencode/bin:$PATH"
|
||||
|
||||
git-safe-delete() {
|
||||
local branch="$1"
|
||||
local base="${2:-main}"
|
||||
|
||||
if [[ -z "$branch" ]]; then
|
||||
echo "Usage: git-safe-delete <branch> [base]"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [[ "$branch" == "$(git branch --show-current)" ]]; then
|
||||
echo "Cannot delete current branch."
|
||||
return 1
|
||||
fi
|
||||
|
||||
git fetch origin >/dev/null 2>&1
|
||||
|
||||
if git diff --quiet "$base..$branch"; then
|
||||
echo "No content differences with $base."
|
||||
git branch -D "$branch"
|
||||
echo "Deleted $branch"
|
||||
else
|
||||
echo "Branch differs from $base."
|
||||
echo "Diff summary:"
|
||||
git diff --stat "$base..$branch"
|
||||
echo
|
||||
read "confirm?Force delete anyway? (y/N): "
|
||||
[[ "$confirm" == "y" ]] && git branch -D "$branch"
|
||||
fi
|
||||
}
|
||||
|
||||
_git_safe_delete() {
|
||||
_arguments \
|
||||
'1:local branch:($(git for-each-ref --format="%(refname:short)" refs/heads))' \
|
||||
'2:base branch (optional):($(git for-each-ref --format="%(refname:short)" refs/heads))'
|
||||
}
|
||||
|
||||
compdef _git_safe_delete git-safe-delete
|
||||
|
||||
|
||||
|
||||
export NVM_DIR="$HOME/.nvm"
|
||||
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm
|
||||
[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion" # This loads nvm bash_completion
|
||||
|
||||
# pnpm
|
||||
export PNPM_HOME="/home/tomas/.local/share/pnpm"
|
||||
case ":$PATH:" in
|
||||
*":$PNPM_HOME:"*) ;;
|
||||
*) export PATH="$PNPM_HOME:$PATH" ;;
|
||||
esac
|
||||
# pnpm end
|
||||
|
||||
Reference in New Issue
Block a user