attempt at new branch helpers
This commit is contained in:
parent
c78adf36b1
commit
09bfa9af4e
5 changed files with 181 additions and 156 deletions
|
|
@ -175,10 +175,61 @@ branch() {
|
||||||
echo "Creating new worktree for branch '$branch_name' at '$wt_path'."
|
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.
|
# 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 [ "$local_exists" -eq 1 ]; then
|
||||||
if git -C "$repo_dir" worktree add "$wt_path" "$branch_name" 2>/dev/null; then
|
if git -C "$repo_dir" worktree add "$wt_path" "$branch_name" 2>/dev/null; then
|
||||||
cd "$wt_path" || return 1
|
cd "$wt_path" || return 1
|
||||||
_branch__maybe_set_tmux_name "$branch_name" "$prev_branch" || true
|
_branch__maybe_set_tmux_name "$branch_name" "$prev_branch" || true
|
||||||
|
_branch__post_setup "$repo_dir" "$wt_path" || true
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
@ -186,6 +237,7 @@ branch() {
|
||||||
if git -C "$repo_dir" worktree add -b "$branch_name" "$wt_path" "$branch_from" 2>/dev/null; then
|
if git -C "$repo_dir" worktree add -b "$branch_name" "$wt_path" "$branch_from" 2>/dev/null; then
|
||||||
cd "$wt_path" || return 1
|
cd "$wt_path" || return 1
|
||||||
_branch__maybe_set_tmux_name "$branch_name" "$prev_branch" || true
|
_branch__maybe_set_tmux_name "$branch_name" "$prev_branch" || true
|
||||||
|
_branch__post_setup "$repo_dir" "$wt_path" || true
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
@ -197,6 +249,7 @@ branch() {
|
||||||
if git -C "$repo_dir" worktree add "$wt_path" "$branch_name" 2>/dev/null; then
|
if git -C "$repo_dir" worktree add "$wt_path" "$branch_name" 2>/dev/null; then
|
||||||
cd "$wt_path" || return 1
|
cd "$wt_path" || return 1
|
||||||
_branch__maybe_set_tmux_name "$branch_name" "$prev_branch" || true
|
_branch__maybe_set_tmux_name "$branch_name" "$prev_branch" || true
|
||||||
|
_branch__post_setup "$repo_dir" "$wt_path" || true
|
||||||
return 0
|
return 0
|
||||||
else
|
else
|
||||||
git -C "$repo_dir" branch -D "$branch_name" 2>/dev/null || true
|
git -C "$repo_dir" branch -D "$branch_name" 2>/dev/null || true
|
||||||
|
|
|
||||||
86
flakes/common/nix_modules/git/branching_setup.func.sh
Normal file
86
flakes/common/nix_modules/git/branching_setup.func.sh
Normal 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
|
||||||
|
}
|
||||||
|
|
@ -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
|
|
||||||
}
|
|
||||||
|
|
@ -19,9 +19,9 @@ with lib;
|
||||||
stashes = "git stash list";
|
stashes = "git stash list";
|
||||||
bd = "branch default";
|
bd = "branch default";
|
||||||
li = "link_ignored";
|
li = "link_ignored";
|
||||||
ci = "copy_ignored";
|
|
||||||
bx = "branchdel";
|
bx = "branchdel";
|
||||||
b = "branch";
|
b = "branch";
|
||||||
|
bs = "branching_setup";
|
||||||
};
|
};
|
||||||
|
|
||||||
environment.shellInit = lib.concatStringsSep "\n\n" [
|
environment.shellInit = lib.concatStringsSep "\n\n" [
|
||||||
|
|
@ -29,6 +29,6 @@ with lib;
|
||||||
(builtins.readFile ./branch.func.sh)
|
(builtins.readFile ./branch.func.sh)
|
||||||
(builtins.readFile ./branchd.func.sh)
|
(builtins.readFile ./branchd.func.sh)
|
||||||
(builtins.readFile ./link_ignored.func.sh)
|
(builtins.readFile ./link_ignored.func.sh)
|
||||||
(builtins.readFile ./copy_ignored.func.sh)
|
(builtins.readFile ./branching_setup.func.sh)
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,14 @@
|
||||||
link_ignored() {
|
link_ignored() {
|
||||||
local DRY_RUN=0
|
local DRY_RUN=0
|
||||||
local USE_FZF=1
|
local USE_FZF=1
|
||||||
|
local AUTO=0
|
||||||
local -a PATTERNS=()
|
local -a PATTERNS=()
|
||||||
|
|
||||||
while [ $# -gt 0 ]; do
|
while [ $# -gt 0 ]; do
|
||||||
case "$1" in
|
case "$1" in
|
||||||
--dry-run) DRY_RUN=1; shift ;;
|
--dry-run) DRY_RUN=1; shift ;;
|
||||||
--no-fzf) USE_FZF=0; shift ;;
|
--no-fzf) USE_FZF=0; shift ;;
|
||||||
|
--auto) AUTO=1; shift ;;
|
||||||
-h|--help) link_ignored_usage; return 0 ;;
|
-h|--help) link_ignored_usage; return 0 ;;
|
||||||
--) shift; break ;;
|
--) shift; break ;;
|
||||||
*) PATTERNS+=("$1"); shift ;;
|
*) PATTERNS+=("$1"); shift ;;
|
||||||
|
|
@ -15,11 +17,16 @@ link_ignored() {
|
||||||
|
|
||||||
link_ignored_usage() {
|
link_ignored_usage() {
|
||||||
cat <<EOF
|
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
|
Interactively or non-interactively create symlinks in the current worktree
|
||||||
for files/dirs that exist in the main repository root but are git-ignored /
|
for files/dirs that exist in the main repository root but are git-ignored /
|
||||||
untracked.
|
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
|
EOF
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -38,6 +45,38 @@ EOF
|
||||||
return 2
|
return 2
|
||||||
fi
|
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=()
|
local -a candidates=()
|
||||||
while IFS= read -r -d '' file; do
|
while IFS= read -r -d '' file; do
|
||||||
candidates+=("$file")
|
candidates+=("$file")
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue