attempt at new branch helpers

This commit is contained in:
RingOfStorms (Joshua Bell) 2025-11-10 16:59:09 -06:00
parent c78adf36b1
commit 09bfa9af4e
5 changed files with 181 additions and 156 deletions

View file

@ -175,10 +175,61 @@ branch() {
echo "Creating new worktree for branch '$branch_name' at '$wt_path'."
# Try to add or update worktree from the resolved ref. Use a fallback path if needed.
_branch__post_setup() {
local repo_dir="$1" wt_path="$2"
# Sentinel in worktree-specific git dir to avoid re-running
local git_dir sentinel
git_dir=$(git -C "$wt_path" rev-parse --git-dir 2>/dev/null || true)
sentinel="$git_dir/post-setup.done"
if [ -f "$sentinel" ]; then
return 0
fi
_branch__auto_link "$repo_dir" "$wt_path" || true
_branch__bootstrap "$repo_dir" "$wt_path" || true
: > "$sentinel" 2>/dev/null || true
}
_branch__auto_link() {
local repo_dir="$1" wt_path="$2"
local has_cfg
has_cfg=$(git -C "$repo_dir" config --get-all worktree.autolink 2>/dev/null | wc -l)
if [ "${BRANCH_AUTOLINK:-0}" -eq 1 ] || [ "$has_cfg" -gt 0 ]; then
if command -v link_ignored >/dev/null 2>&1; then
( cd "$wt_path" && link_ignored --auto --no-fzf ) || true
fi
fi
}
_branch__bootstrap() {
local repo_dir="$1" wt_path="$2"
local mode cmd
mode=$(git -C "$repo_dir" config --get worktree.bootstrap 2>/dev/null || true)
if [ -n "${BRANCH_BOOTSTRAP_CMD:-}" ]; then
cmd="$BRANCH_BOOTSTRAP_CMD"
elif [ -n "$mode" ]; then
cmd="$mode"
else
case "${BRANCH_BOOTSTRAP:-skip}" in
auto)
if [ -f "$wt_path/pnpm-lock.yaml" ]; then cmd="pnpm i --frozen-lockfile"
elif [ -f "$wt_path/yarn.lock" ]; then cmd="yarn install --frozen-lockfile || yarn install --immutable"
elif [ -f "$wt_path/package-lock.json" ]; then cmd="npm ci"
else cmd=""; fi
;;
skip|0|false) cmd="" ;;
1|true) cmd="npm ci" ;;
esac
fi
[ -z "$cmd" ] && return 0
( cd "$wt_path" && eval "$cmd" ) || true
}
if [ "$local_exists" -eq 1 ]; then
if git -C "$repo_dir" worktree add "$wt_path" "$branch_name" 2>/dev/null; then
cd "$wt_path" || return 1
_branch__maybe_set_tmux_name "$branch_name" "$prev_branch" || true
_branch__post_setup "$repo_dir" "$wt_path" || true
return 0
fi
@ -186,6 +237,7 @@ branch() {
if git -C "$repo_dir" worktree add -b "$branch_name" "$wt_path" "$branch_from" 2>/dev/null; then
cd "$wt_path" || return 1
_branch__maybe_set_tmux_name "$branch_name" "$prev_branch" || true
_branch__post_setup "$repo_dir" "$wt_path" || true
return 0
fi
fi
@ -197,6 +249,7 @@ branch() {
if git -C "$repo_dir" worktree add "$wt_path" "$branch_name" 2>/dev/null; then
cd "$wt_path" || return 1
_branch__maybe_set_tmux_name "$branch_name" "$prev_branch" || true
_branch__post_setup "$repo_dir" "$wt_path" || true
return 0
else
git -C "$repo_dir" branch -D "$branch_name" 2>/dev/null || true

View file

@ -0,0 +1,86 @@
branching_setup() {
# Interactive helper to manage worktree.autolink and worktree.bootstrap
local common_dir repo_root
if ! common_dir=$(git rev-parse --git-common-dir 2>/dev/null); then
echo "Not inside a git repository." >&2
return 1
fi
if [ "${common_dir#/}" = "$common_dir" ]; then
common_dir="$(pwd)/$common_dir"
fi
repo_root="${common_dir%%/.git*}"
if [ -z "$repo_root" ]; then
echo "Unable to determine repository root." >&2
return 1
fi
# Build candidate ignored/untracked file list
local -a candidates=()
while IFS= read -r -d '' file; do
candidates+=("$file")
done < <(git -C "$repo_root" ls-files --others --ignored --exclude-standard -z || true)
# Include some common dotfiles at root even if tracked (for selection convenience)
for extra in .env .env.development .env.development.local .envrc .direnv flake.nix flake.lock; do
if [ -e "$repo_root/$extra" ]; then
candidates+=("$extra")
fi
done
# De-duplicate
local unique
unique=$(printf "%s\n" "${candidates[@]}" | awk '!seen[$0]++')
# Current config values
local -a current
while IFS= read -r line; do
[ -n "$line" ] && current+=("$line")
done < <(git -C "$repo_root" config --get-all worktree.autolink 2>/dev/null || true)
# Preselect current ones in fzf (mark with *)
local list
list=$(printf "%s\n" $unique | while read -r x; do
local mark=""
for c in "${current[@]}"; do [ "$c" = "$x" ] && mark="*" && break; done
printf "%s%s\n" "$mark" "$x"
done)
if ! command -v fzf >/dev/null 2>&1; then
echo "fzf not found; printing candidates. Use git config --local --add worktree.autolink <item> to add." >&2
printf "%s\n" "$unique"
else
local selection
selection=$(printf "%s\n" "$list" | sed 's/^\*//' | fzf --multi --prompt="Select autolink items: " --preview "if [ -f '$repo_root'/{} ]; then bat --color always --paging=never --style=plain '$repo_root'/{}; else ls -la '$repo_root'/{}; fi")
# Reset existing values
git -C "$repo_root" config --unset-all worktree.autolink 2>/dev/null || true
# Apply selection
if [ -n "$selection" ]; then
while IFS= read -r line; do
[ -n "$line" ] && git -C "$repo_root" config --add worktree.autolink "$line"
done <<EOF
$selection
EOF
fi
echo "Updated worktree.autolink entries."
fi
# Bootstrap mode
echo "\nBootstrap setup"
local current_bootstrap
current_bootstrap=$(git -C "$repo_root" config --get worktree.bootstrap 2>/dev/null || printf "")
echo "Current: ${current_bootstrap:-<none>}"
echo "Options: [skip] [auto] [custom command]"
local choice
if [ -n "$ZSH_VERSION" ]; then
read -r "choice?Enter bootstrap mode or command: "
else
read -r -p "Enter bootstrap mode or command: " choice
fi
choice=${choice:-$current_bootstrap}
if [ -z "$choice" ]; then
echo "Leaving bootstrap unchanged."
else
git -C "$repo_root" config worktree.bootstrap "$choice"
echo "Set worktree.bootstrap=$choice"
fi
}

View file

@ -1,153 +0,0 @@
copy_ignored() {
local DRY_RUN=0
local USE_FZF=1
local -a PATTERNS=()
while [ $# -gt 0 ]; do
case "$1" in
--dry-run) DRY_RUN=1; shift ;;
--no-fzf) USE_FZF=0; shift ;;
-h|--help) copy_ignored_usage; return 0 ;;
--) shift; break ;;
*) PATTERNS+=("$1"); shift ;;
esac
done
copy_ignored_usage() {
cat <<EOF
Usage: copy_ignored [--dry-run] [--no-fzf] [pattern ...]
Interactively or non-interactively copy files/dirs into the current worktree
for files/dirs that exist in the main repository root but are git-ignored /
untracked.
EOF
}
# Determine the main repo root using git-common-dir (handles worktrees)
local common_dir repo_root
if ! common_dir=$(git rev-parse --git-common-dir 2>/dev/null); then
echo "Error: not in a git repository." >&2
return 2
fi
if [ "${common_dir#/}" = "$common_dir" ]; then
common_dir="$(pwd)/$common_dir"
fi
repo_root="${common_dir%%/.git*}"
if [ -z "$repo_root" ]; then
echo "Error: unable to determine repository root." >&2
return 2
fi
local -a candidates=()
while IFS= read -r -d '' file; do
candidates+=("$file")
done < <(git -C "$repo_root" ls-files --others --ignored --exclude-standard -z || true)
if [ ${#candidates[@]} -eq 0 ]; then
echo "No untracked/ignored files found in $repo_root"
return 0
fi
local -a tops=()
for c in "${candidates[@]}"; do
c="${c%/}"
local top="${c%%/*}"
[ -z "$top" ] && continue
local found=0
for existing in "${tops[@]}"; do
[ "$existing" = "$top" ] && found=1 && break
done
[ "$found" -eq 0 ] && tops+=("$top")
done
if [ ${#tops[@]} -eq 0 ]; then
echo "No top-level ignored/untracked entries found in $repo_root"
return 0
fi
local -a filtered
if [ ${#PATTERNS[@]} -gt 0 ]; then
for t in "${tops[@]}"; do
for p in "${PATTERNS[@]}"; do
if [[ "$t" == *"$p"* ]]; then
filtered+=("$t")
break
fi
done
done
else
filtered=("${tops[@]}")
fi
if [ ${#filtered[@]} -eq 0 ]; then
echo "No candidates match the provided patterns." >&2
return 0
fi
local -a chosen
if command -v fzf >/dev/null 2>&1 && [ "$USE_FZF" -eq 1 ]; then
local selected
selected=$(printf "%s\n" "${filtered[@]}" | fzf --multi --height=40% --border --prompt="Select files to copy: " --preview "if [ -f '$repo_root'/{} ]; then bat --color always --paging=never --style=plain '$repo_root'/{}; else ls -la '$repo_root'/{}; fi")
if [ -z "$selected" ]; then
echo "No files selected." && return 0
fi
chosen=()
while IFS= read -r line; do
chosen+=("$line")
done <<EOF
$selected
EOF
else
chosen=("${filtered[@]}")
fi
local worktree_root
worktree_root=$(pwd)
echo "Repository root: $repo_root"
echo "Worktree root : $worktree_root"
local -a created=()
local -a skipped=()
local -a errors=()
for rel in "${chosen[@]}"; do
rel=${rel%%$'\n'}
local src="${repo_root}/${rel}"
local dst="${worktree_root}/${rel}"
if [ ! -e "$src" ]; then
errors+=("$rel (source missing)")
continue
fi
if [ -e "$dst" ]; then
echo "Skipping $rel (destination exists)"
skipped+=("$rel")
continue
fi
mkdir -p "$(dirname "$dst")"
if [ "$DRY_RUN" -eq 1 ]; then
if [ -d "$src" ]; then
echo "DRY RUN: cp -r '$src' '$dst'"
else
echo "DRY RUN: cp '$src' '$dst'"
fi
else
local copy_result=0
if [ -d "$src" ]; then
if cp -r "$src" "$dst"; then
copy_result=0
else
copy_result=1
fi
else
if cp "$src" "$dst"; then
copy_result=0
else
copy_result=1
fi
fi
if [ "$copy_result" -eq 0 ]; then
echo "Copied: $rel"
created+=("$rel")
else
echo "Failed to copy: $rel" >&2
errors+=("$rel (copy failed)")
fi
fi
done
echo
echo "Summary:"
echo " Copied: ${#created[@]}"
[ ${#created[@]} -gt 0 ] && printf ' %s\n' "${created[@]}"
echo " Skipped: ${#skipped[@]}"
[ ${#skipped[@]} -gt 0 ] && printf ' %s\n' "${skipped[@]}"
echo " Errors: ${#errors[@]}"
[ ${#errors[@]} -gt 0 ] && printf ' %s\n' "${errors[@]}"
return 0
}

View file

@ -19,9 +19,9 @@ with lib;
stashes = "git stash list";
bd = "branch default";
li = "link_ignored";
ci = "copy_ignored";
bx = "branchdel";
b = "branch";
bs = "branching_setup";
};
environment.shellInit = lib.concatStringsSep "\n\n" [
@ -29,6 +29,6 @@ with lib;
(builtins.readFile ./branch.func.sh)
(builtins.readFile ./branchd.func.sh)
(builtins.readFile ./link_ignored.func.sh)
(builtins.readFile ./copy_ignored.func.sh)
(builtins.readFile ./branching_setup.func.sh)
];
}

View file

@ -1,12 +1,14 @@
link_ignored() {
local DRY_RUN=0
local USE_FZF=1
local AUTO=0
local -a PATTERNS=()
while [ $# -gt 0 ]; do
case "$1" in
--dry-run) DRY_RUN=1; shift ;;
--no-fzf) USE_FZF=0; shift ;;
--auto) AUTO=1; shift ;;
-h|--help) link_ignored_usage; return 0 ;;
--) shift; break ;;
*) PATTERNS+=("$1"); shift ;;
@ -15,11 +17,16 @@ link_ignored() {
link_ignored_usage() {
cat <<EOF
Usage: link_ignored [--dry-run] [--no-fzf] [pattern ...]
Usage: link_ignored [--dry-run] [--no-fzf] [--auto] [pattern ...]
Interactively or non-interactively create symlinks in the current worktree
for files/dirs that exist in the main repository root but are git-ignored /
untracked.
Defaults:
- If no patterns provided, tries git config worktree.autolink (multi)
- Else falls back to env LINK_IGNORED_DEFAULTS (space-separated)
- With --auto and defaults present, skips fzf and links immediately
EOF
}
@ -38,6 +45,38 @@ EOF
return 2
fi
_li_load_defaults() {
local -a cfg=()
while IFS= read -r line; do
[ -n "$line" ] && cfg+=("$line")
done < <(git -C "$repo_root" config --get-all worktree.autolink 2>/dev/null || true)
if [ ${#cfg[@]} -gt 0 ]; then
PATTERNS=("${cfg[@]}")
return 0
fi
if [ -n "${LINK_IGNORED_DEFAULTS:-}" ]; then
if [ -n "${ZSH_VERSION:-}" ]; then
eval "PATTERNS=(${=LINK_IGNORED_DEFAULTS})"
else
read -r -a PATTERNS <<< "$LINK_IGNORED_DEFAULTS"
fi
return 0
fi
return 1
}
# Try to load defaults if none provided
if [ ${#PATTERNS[@]} -eq 0 ]; then
_li_load_defaults || true
fi
# If AUTO requested and we have patterns, skip fzf
if [ $AUTO -eq 1 ] && [ ${#PATTERNS[@]} -gt 0 ]; then
USE_FZF=0
fi
local -a candidates=()
while IFS= read -r -d '' file; do
candidates+=("$file")