From 09bfa9af4ebe3fdc075180cd3434567d1b5d2117 Mon Sep 17 00:00:00 2001 From: "RingOfStorms (Joshua Bell)" Date: Mon, 10 Nov 2025 16:59:09 -0600 Subject: [PATCH] attempt at new branch helpers --- flakes/common/nix_modules/git/branch.func.sh | 53 ++++++ .../nix_modules/git/branching_setup.func.sh | 86 ++++++++++ .../nix_modules/git/copy_ignored.func.sh | 153 ------------------ flakes/common/nix_modules/git/default.nix | 4 +- .../nix_modules/git/link_ignored.func.sh | 41 ++++- 5 files changed, 181 insertions(+), 156 deletions(-) create mode 100644 flakes/common/nix_modules/git/branching_setup.func.sh delete mode 100644 flakes/common/nix_modules/git/copy_ignored.func.sh diff --git a/flakes/common/nix_modules/git/branch.func.sh b/flakes/common/nix_modules/git/branch.func.sh index 03908fc..3d6e26c 100644 --- a/flakes/common/nix_modules/git/branch.func.sh +++ b/flakes/common/nix_modules/git/branch.func.sh @@ -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 diff --git a/flakes/common/nix_modules/git/branching_setup.func.sh b/flakes/common/nix_modules/git/branching_setup.func.sh new file mode 100644 index 0000000..c0efbb3 --- /dev/null +++ b/flakes/common/nix_modules/git/branching_setup.func.sh @@ -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 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 </dev/null || printf "") + echo "Current: ${current_bootstrap:-}" + 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 +} diff --git a/flakes/common/nix_modules/git/copy_ignored.func.sh b/flakes/common/nix_modules/git/copy_ignored.func.sh deleted file mode 100644 index 338c4d2..0000000 --- a/flakes/common/nix_modules/git/copy_ignored.func.sh +++ /dev/null @@ -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 </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 <&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 -} diff --git a/flakes/common/nix_modules/git/default.nix b/flakes/common/nix_modules/git/default.nix index 0c0c2f1..3a4c5bc 100644 --- a/flakes/common/nix_modules/git/default.nix +++ b/flakes/common/nix_modules/git/default.nix @@ -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) ]; } diff --git a/flakes/common/nix_modules/git/link_ignored.func.sh b/flakes/common/nix_modules/git/link_ignored.func.sh index 0043ed9..0e100ec 100644 --- a/flakes/common/nix_modules/git/link_ignored.func.sh +++ b/flakes/common/nix_modules/git/link_ignored.func.sh @@ -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 </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")