new git working strategy, using worktrees now for easier parallel development
This commit is contained in:
parent
186e8db249
commit
951dd38e9d
8 changed files with 320 additions and 385 deletions
105
common/general/shell/branch.func.sh
Normal file
105
common/general/shell/branch.func.sh
Normal file
|
|
@ -0,0 +1,105 @@
|
||||||
|
branch() {
|
||||||
|
local branch_name=${1:-}
|
||||||
|
if [ -z "$branch_name" ]; then
|
||||||
|
echo "Usage: branch <name>" >&2
|
||||||
|
return 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
# branch <name> — create or open a worktree for <name>
|
||||||
|
# This function will change directory into the selected worktree so the
|
||||||
|
# caller's shell is moved into it.
|
||||||
|
|
||||||
|
local xdg=${XDG_DATA_HOME:-$HOME/.local/share}
|
||||||
|
|
||||||
|
local common_dir
|
||||||
|
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
|
||||||
|
local repo_dir="${common_dir%%/.git*}"
|
||||||
|
if [ -z "$repo_dir" ]; then
|
||||||
|
echo "Unable to determine repository root." >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
local repo_base repo_hash default_branch
|
||||||
|
repo_base=$(basename "$repo_dir")
|
||||||
|
repo_hash=$(printf "%s" "$repo_dir" | sha1sum | awk '{print $1}')
|
||||||
|
|
||||||
|
default_branch=$(getdefault)
|
||||||
|
|
||||||
|
# Special-case: jump to the main working tree on the default branch
|
||||||
|
if [ "$branch_name" = "default" ] || [ "$branch_name" = "master" ] || [ "$branch_name" = "$default_branch" ]; then
|
||||||
|
if [ "$repo_dir" = "$PWD" ]; then
|
||||||
|
echo "Already in the main working tree on branch '$default_branch'."
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
echo "Switching to main working tree on branch '$default_branch'."
|
||||||
|
cd "$repo_dir" || return 1
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# If a worktree for this branch is already registered elsewhere, open a shell there
|
||||||
|
local existing
|
||||||
|
existing=$(git -C "$repo_dir" worktree list --porcelain 2>/dev/null | awk -v b="$branch_name" 'BEGIN{RS="";FS="\n"} $0 ~ "refs/heads/"b{for(i=1;i<=NF;i++) if ($i ~ /^worktree /){ sub(/^worktree /,"",$i); print $i }}')
|
||||||
|
if [ -n "$existing" ]; then
|
||||||
|
echo "Opening existing worktree for branch '$branch_name' at '$existing'."
|
||||||
|
cd "$existing" || return 1
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Ensure we have up-to-date remote info
|
||||||
|
git -C "$repo_dir" fetch --all --prune || true
|
||||||
|
|
||||||
|
local wt_root wt_path
|
||||||
|
wt_root="$xdg/git_worktrees/${repo_base}_${repo_hash}"
|
||||||
|
wt_path="$wt_root/$branch_name"
|
||||||
|
|
||||||
|
# If worktree already exists at our expected path, open a shell there
|
||||||
|
if [ -d "$wt_path" ]; then
|
||||||
|
echo "Opening existing worktree at '$wt_path'."
|
||||||
|
cd "$wt_path" || return 1
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
local branch_exists branch_from
|
||||||
|
branch_exists=$(git -C "$repo_dir" ls-remote --heads origin "$branch_name" | wc -l)
|
||||||
|
branch_from="$default_branch"
|
||||||
|
if [ "$branch_exists" -eq 0 ]; then
|
||||||
|
echo "Branch '$branch_name' does not exist on remote; creating from '$branch_from'."
|
||||||
|
else
|
||||||
|
branch_from="origin/$branch_name"
|
||||||
|
echo "Branch '$branch_name' exists on remote; creating worktree tracking it."
|
||||||
|
fi
|
||||||
|
|
||||||
|
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.
|
||||||
|
if git -C "$repo_dir" worktree add -b "$branch_name" "$wt_path" "$branch_from" 2>/dev/null; then
|
||||||
|
cd "$wt_path" || return 1
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Fallback: try to resolve a concrete SHA and create the branch ref locally, then add worktree
|
||||||
|
local start_sha
|
||||||
|
if start_sha=$(git -C "$repo_dir" rev-parse --verify "$branch_from" 2>/dev/null); then
|
||||||
|
if git -C "$repo_dir" branch "$branch_name" "$start_sha" 2>/dev/null; then
|
||||||
|
if git -C "$repo_dir" worktree add "$wt_path" "$branch_name" 2>/dev/null; then
|
||||||
|
cd "$wt_path" || return 1
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
git -C "$repo_dir" branch -D "$branch_name" 2>/dev/null || true
|
||||||
|
rmdir "$wt_path" 2>/dev/null || true
|
||||||
|
echo "Failed to add worktree after creating branch ref." >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Failed to add worktree for branch '$branch_name'." >&2
|
||||||
|
rmdir "$wt_path" 2>/dev/null || true
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
@ -1,113 +0,0 @@
|
||||||
# Branch and branchd helpers (worktree-based)
|
|
||||||
|
|
||||||
# branch <name> — create or jump to a worktree for <name>
|
|
||||||
branch() {
|
|
||||||
# Use XDG_DATA_HOME or default to ~/.local/share
|
|
||||||
local xdg=${XDG_DATA_HOME:-$HOME/.local/share}
|
|
||||||
# Determine the git common dir (points into the main repo's .git)
|
|
||||||
local common_dir
|
|
||||||
common_dir=$(git rev-parse --git-common-dir 2>/dev/null) || {
|
|
||||||
echo "Not inside a git repository." >&2
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
# Make common_dir absolute if it's relative
|
|
||||||
if [ "${common_dir#/}" = "$common_dir" ]; then
|
|
||||||
common_dir="$(pwd)/$common_dir"
|
|
||||||
fi
|
|
||||||
# repo_dir is the path before '/.git' in the common_dir (handles worktrees)
|
|
||||||
local repo_dir
|
|
||||||
repo_dir="${common_dir%%/.git*}"
|
|
||||||
if [ -z "$repo_dir" ]; then
|
|
||||||
echo "Unable to determine repository root." >&2
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
local repo_base
|
|
||||||
repo_base=$(basename "$repo_dir")
|
|
||||||
local repo_hash
|
|
||||||
repo_hash=$(printf "%s" "$repo_dir" | sha1sum | awk '{print $1}')
|
|
||||||
|
|
||||||
local branch_name
|
|
||||||
branch_name=$1
|
|
||||||
if [ -z "$branch_name" ]; then
|
|
||||||
echo "Usage: branch <name>" >&2
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# If user asked for default or master, cd back to repo root on default branch
|
|
||||||
local default_branch
|
|
||||||
default_branch=$(getdefault 2>/dev/null || echo "")
|
|
||||||
if [ "$branch_name" = "default" ] || [ "$branch_name" = "master" ] || [ "$branch_name" = "$default_branch" ]; then
|
|
||||||
cd "$repo_dir" || return 0
|
|
||||||
git fetch
|
|
||||||
git checkout "$default_branch"
|
|
||||||
pull
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Ensure we have up-to-date remote info
|
|
||||||
git fetch --all --prune
|
|
||||||
|
|
||||||
# If branch exists remotely and not locally, create local branch tracking remote
|
|
||||||
if git ls-remote --exit-code --heads origin "$branch_name" >/dev/null 2>&1; then
|
|
||||||
if ! git show-ref --verify --quiet "refs/heads/$branch_name"; then
|
|
||||||
git branch --track "$branch_name" "origin/$branch_name" 2>/dev/null || git branch "$branch_name" "origin/$branch_name"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Worktree path
|
|
||||||
local wt_root
|
|
||||||
wt_root="$xdg/git_worktrees/${repo_base}_${repo_hash}"
|
|
||||||
local wt_path
|
|
||||||
wt_path="$wt_root/$branch_name"
|
|
||||||
|
|
||||||
mkdir -p "$wt_root"
|
|
||||||
|
|
||||||
# If worktree already exists at our expected path, cd to it
|
|
||||||
if [ -d "$wt_path/.git" ]; then
|
|
||||||
cd "$wt_path" || return 0
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
# If a worktree for this branch is already registered elsewhere, find it and cd
|
|
||||||
local existing
|
|
||||||
existing=$(git worktree list --porcelain 2>/dev/null | awk -v b="$branch_name" 'BEGIN{RS=""} $0 ~ "refs/heads/"b{for(i=1;i<=NF;i++) if ($i ~ /^worktree/) print $2 }')
|
|
||||||
if [ -n "$existing" ]; then
|
|
||||||
cd "$existing" || return 0
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Create the worktree
|
|
||||||
mkdir -p "$wt_path"
|
|
||||||
git worktree add -B "$branch_name" "$wt_path" "origin/$branch_name" 2>/dev/null || git worktree add "$wt_path" "$branch_name"
|
|
||||||
cd "$wt_path" || return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
# branchd — remove current branch worktree
|
|
||||||
branchd() {
|
|
||||||
local current
|
|
||||||
current=$(git rev-parse --abbrev-ref HEAD 2>/dev/null) || {
|
|
||||||
echo "Not inside a git repository." >&2
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
local default_branch
|
|
||||||
default_branch=$(getdefault 2>/dev/null || echo "")
|
|
||||||
|
|
||||||
if [ "$current" = "$default_branch" ] || [ "$current" = "default" ]; then
|
|
||||||
echo "Already on default branch ($default_branch). Won't remove." >&2
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Find the worktree path for the current branch
|
|
||||||
local wt_path
|
|
||||||
wt_path=$(git worktree list --porcelain 2>/dev/null | awk -v b="$current" 'BEGIN{RS="";FS="\n"} $0 ~ "refs/heads/"b{for(i=1;i<=NF;i++) if ($i ~ /^worktree /) { sub(/^worktree /,"",$i); print $i }}')
|
|
||||||
if [ -z "$wt_path" ]; then
|
|
||||||
echo "Worktree for branch '$current' not found." >&2
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Switch to default branch (uses branch() helper) and then remove worktree
|
|
||||||
branch default || { echo "Failed to switch to default branch" >&2; return 1; }
|
|
||||||
|
|
||||||
git worktree remove "$wt_path"
|
|
||||||
}
|
|
||||||
55
common/general/shell/branchd.func.sh
Normal file
55
common/general/shell/branchd.func.sh
Normal file
|
|
@ -0,0 +1,55 @@
|
||||||
|
branchdel() {
|
||||||
|
# branchd — remove current branch worktree (function form)
|
||||||
|
local wt_path
|
||||||
|
wt_path=$(pwd)
|
||||||
|
local common_dir repo_dir
|
||||||
|
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_dir="${common_dir%%/.git*}"
|
||||||
|
if [ -z "$repo_dir" ]; then
|
||||||
|
echo "Unable to determine repository root." >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$repo_dir" = "$wt_path" ]; then
|
||||||
|
echo "Inside the root directory of repo, will not delete." >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
local current default_branch
|
||||||
|
current=$(git rev-parse --abbrev-ref HEAD 2>/dev/null) || { echo "Not inside a git repository." >&2; return 1; }
|
||||||
|
|
||||||
|
default_branch=$(getdefault)
|
||||||
|
|
||||||
|
if [ "$current" = "$default_branch" ] || [ "$current" = "default" ]; then
|
||||||
|
echo "Already on default branch ($default_branch). Won't remove."
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Switching to default branch '$default_branch'..."
|
||||||
|
# Use branch function if available, otherwise checkout directly in repo
|
||||||
|
if declare -f branch >/dev/null 2>&1; then
|
||||||
|
branch default || { echo "Failed to switch to default branch" >&2; return 1; }
|
||||||
|
else
|
||||||
|
git -C "$repo_dir" checkout "$default_branch" || { echo "Failed to checkout default branch" >&2; return 1; }
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Removing worktree at: $wt_path"
|
||||||
|
if git -C "$repo_dir" worktree remove "$wt_path" 2>/dev/null; then
|
||||||
|
echo "Removed worktree: $wt_path"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
# try with --force as a fallback
|
||||||
|
if git -C "$repo_dir" worktree remove --force "$wt_path" 2>/dev/null; then
|
||||||
|
echo "Removed worktree (forced): $wt_path"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Failed to remove worktree: $wt_path" >&2
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
@ -25,17 +25,6 @@ with lib;
|
||||||
hdparm
|
hdparm
|
||||||
speedtest-cli
|
speedtest-cli
|
||||||
lf
|
lf
|
||||||
|
|
||||||
# Build script bins from repo scripts
|
|
||||||
(pkgs.writeShellScriptBin "branch" ''#!/usr/bin/env bash
|
|
||||||
. ${./branch.sh}
|
|
||||||
branch "$@"
|
|
||||||
'')
|
|
||||||
(pkgs.writeShellScriptBin "branchd" ''#!/usr/bin/env bash
|
|
||||||
. ${./branch.sh}
|
|
||||||
branchd "$@"
|
|
||||||
'')
|
|
||||||
(pkgs.writeShellScriptBin "link_ignored" (builtins.readFile ./link_ignored.sh))
|
|
||||||
];
|
];
|
||||||
|
|
||||||
environment.shellAliases = {
|
environment.shellAliases = {
|
||||||
|
|
@ -64,12 +53,16 @@ branchd "$@"
|
||||||
gcm = "git commit -m";
|
gcm = "git commit -m";
|
||||||
stashes = "git stash list";
|
stashes = "git stash list";
|
||||||
|
|
||||||
|
|
||||||
# ripgrep
|
# ripgrep
|
||||||
rg = "rg --no-ignore";
|
rg = "rg --no-ignore";
|
||||||
rgf = "rg --files --glob '!/nix/store/**' 2>/dev/null | rg";
|
rgf = "rg --files --glob '!/nix/store/**' 2>/dev/null | rg";
|
||||||
};
|
};
|
||||||
|
|
||||||
environment.shellInit = builtins.readFile ./common.sh;
|
environment.shellInit = lib.concatStringsSep "\n\n" [
|
||||||
|
(builtins.readFile ./common.sh)
|
||||||
|
(builtins.readFile ./branch.func.sh)
|
||||||
|
(builtins.readFile ./branchd.func.sh)
|
||||||
|
(builtins.readFile ./link_ignored.func.sh)
|
||||||
|
];
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -71,7 +71,7 @@ getdefault () {
|
||||||
}
|
}
|
||||||
|
|
||||||
master () {
|
master () {
|
||||||
git stash
|
branch $(getdefault)
|
||||||
git checkout $(getdefault)
|
git checkout $(getdefault)
|
||||||
pull
|
pull
|
||||||
}
|
}
|
||||||
|
|
@ -129,89 +129,6 @@ prunel () {
|
||||||
done;
|
done;
|
||||||
}
|
}
|
||||||
|
|
||||||
branch() {
|
|
||||||
local branch_name
|
|
||||||
branch_name=$1
|
|
||||||
if [ -z "$branch_name" ]; then
|
|
||||||
echo "Usage: branch <name>" >&2
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Use XDG_DATA_HOME or default to ~/.local/share
|
|
||||||
local xdg=${XDG_DATA_HOME:-$HOME/.local/share}
|
|
||||||
local repo_dir
|
|
||||||
# Determine the git common dir (points into the main repo's .git)
|
|
||||||
local common_dir
|
|
||||||
common_dir=$(git rev-parse --git-common-dir 2>/dev/null) || {
|
|
||||||
echo "Not inside a git repository." >&2
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
# Make common_dir absolute if it's relative
|
|
||||||
if [ "${common_dir#/}" = "$common_dir" ]; then
|
|
||||||
common_dir="$(pwd)/$common_dir"
|
|
||||||
fi
|
|
||||||
# repo_dir is the path before '/.git' in the common_dir (handles worktrees)
|
|
||||||
repo_dir="${common_dir%%/.git*}"
|
|
||||||
if [ -z "$repo_dir" ]; then
|
|
||||||
echo "Unable to determine repository root." >&2
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
local repo_base
|
|
||||||
repo_base=$(basename "$repo_dir")
|
|
||||||
local repo_hash
|
|
||||||
repo_hash=$(printf "%s" "$repo_dir" | sha1sum | awk '{print $1}')
|
|
||||||
|
|
||||||
# If user asked for default or master, cd back to repo root on default branch
|
|
||||||
local default_branch
|
|
||||||
default_branch=$(getdefault 2>/dev/null || echo "")
|
|
||||||
if [ "$branch_name" = "default" ] || [ "$branch_name" = "master" ] || [ "$branch_name" = "$default_branch" ]; then
|
|
||||||
cd "$repo_dir" || return 0
|
|
||||||
# git fetch
|
|
||||||
# git checkout "$default_branch"
|
|
||||||
# pull
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Ensure we have up-to-date remote info
|
|
||||||
git fetch --all --prune
|
|
||||||
|
|
||||||
# If branch exists remotely and not locally, create local branch tracking remote
|
|
||||||
if git ls-remote --exit-code --heads origin "$branch_name" >/dev/null 2>&1; then
|
|
||||||
if ! git show-ref --verify --quiet "refs/heads/$branch_name"; then
|
|
||||||
git branch --track "$branch_name" "origin/$branch_name" 2>/dev/null || git branch "$branch_name" "origin/$branch_name"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Worktree path
|
|
||||||
local wt_root
|
|
||||||
wt_root="$xdg/git_worktrees/${repo_base}_${repo_hash}"
|
|
||||||
local wt_path
|
|
||||||
wt_path="$wt_root/$branch_name"
|
|
||||||
|
|
||||||
mkdir -p "$wt_root"
|
|
||||||
|
|
||||||
# If worktree already exists, cd to it
|
|
||||||
if [ -d "$wt_path/.git" ] || [ -d "$wt_path" -a -d "$wt_path/.git" ]; then
|
|
||||||
cd "$wt_path" || return 0
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
# If a worktree for this branch is already registered elsewhere, find it and cd
|
|
||||||
local existing
|
|
||||||
existing=$(git worktree list --porcelain 2>/dev/null | awk -v b="$branch_name" 'BEGIN{RS=""} $0 ~ "refs/heads/"b{for(i=1;i<=NF;i++) if ($i ~ /^worktree/) print $2 }')
|
|
||||||
if [ -n "$existing" ]; then
|
|
||||||
cd "$existing" || return 0
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Create the worktree
|
|
||||||
mkdir -p "$wt_path"
|
|
||||||
git worktree add -B "$branch_name" "$wt_path" "origin/$branch_name" 2>/dev/null || git worktree add "$wt_path" "$branch_name"
|
|
||||||
cd "$wt_path" || return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
from_master () {
|
from_master () {
|
||||||
git checkout $(getdefault) $@
|
git checkout $(getdefault) $@
|
||||||
}
|
}
|
||||||
|
|
|
||||||
152
common/general/shell/link_ignored.func.sh
Normal file
152
common/general/shell/link_ignored.func.sh
Normal file
|
|
@ -0,0 +1,152 @@
|
||||||
|
link_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) link_ignored_usage; return 0 ;;
|
||||||
|
--) shift; break ;;
|
||||||
|
*) PATTERNS+=("$1"); shift ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
link_ignored_usage() {
|
||||||
|
cat <<EOF
|
||||||
|
Usage: link_ignored [--dry-run] [--no-fzf] [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.
|
||||||
|
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
|
||||||
|
IFS=$'\0' read -r -d '' -a candidates < <(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
|
||||||
|
|
||||||
|
declare -A _seen
|
||||||
|
local -a tops=()
|
||||||
|
for c in "${candidates[@]}"; do
|
||||||
|
c="${c%/}"
|
||||||
|
local top="${c%%/*}"
|
||||||
|
[ -z "$top" ] && continue
|
||||||
|
if [ -z "${_seen[$top]:-}" ]; then
|
||||||
|
_seen[$top]=1
|
||||||
|
tops+=("$top")
|
||||||
|
fi
|
||||||
|
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 link: " --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
|
||||||
|
IFS=$'\n' read -r -d '' -a chosen < <(printf "%s\n" "$selected" && printf '\0')
|
||||||
|
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 [ -L "$dst" ]; then
|
||||||
|
echo "Skipping $rel (already symlink)"
|
||||||
|
skipped+=("$rel")
|
||||||
|
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
|
||||||
|
echo "DRY RUN: ln -s '$src' '$dst'"
|
||||||
|
else
|
||||||
|
if ln -s "$src" "$dst"; then
|
||||||
|
echo "Linked: $rel"
|
||||||
|
created+=("$rel")
|
||||||
|
else
|
||||||
|
echo "Failed to link: $rel" >&2
|
||||||
|
errors+=("$rel (link failed)")
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "Summary:"
|
||||||
|
echo " Linked: ${#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
|
||||||
|
}
|
||||||
|
|
@ -1,175 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
usage() {
|
|
||||||
cat <<EOF
|
|
||||||
Usage: link_ignored.sh [--dry-run] [--no-fzf] [pattern ...]
|
|
||||||
|
|
||||||
Description:
|
|
||||||
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. Useful for syncing local dev files (eg .env, .envrc) from your
|
|
||||||
main repo working copy into a worktree.
|
|
||||||
|
|
||||||
Options:
|
|
||||||
--dry-run : Print actions but don't create symlinks
|
|
||||||
--no-fzf : Don't use fzf for interactive selection; select all matching
|
|
||||||
pattern ... : Optional glob or substring patterns to filter candidate paths
|
|
||||||
|
|
||||||
Examples:
|
|
||||||
link_ignored.sh .env .envrc
|
|
||||||
link_ignored.sh --dry-run
|
|
||||||
link_ignored.sh --no-fzf
|
|
||||||
EOF
|
|
||||||
}
|
|
||||||
|
|
||||||
DRY_RUN=0
|
|
||||||
USE_FZF=1
|
|
||||||
PATTERNS=()
|
|
||||||
|
|
||||||
while [ $# -gt 0 ]; do
|
|
||||||
case "$1" in
|
|
||||||
--dry-run) DRY_RUN=1; shift ;;
|
|
||||||
--no-fzf) USE_FZF=0; shift ;;
|
|
||||||
-h|--help) usage; exit 0 ;;
|
|
||||||
--) shift; break ;;
|
|
||||||
*) PATTERNS+=("$1"); shift ;;
|
|
||||||
esac
|
|
||||||
done
|
|
||||||
|
|
||||||
# Determine the main repo root using git-common-dir (handles worktrees)
|
|
||||||
if ! common_dir=$(git rev-parse --git-common-dir 2>/dev/null); then
|
|
||||||
echo "Error: not in a git repository." >&2
|
|
||||||
exit 2
|
|
||||||
fi
|
|
||||||
# Make absolute if relative
|
|
||||||
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
|
|
||||||
exit 2
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Get list of untracked/ignored files from the repo root (relative paths)
|
|
||||||
mapfile -d $'\0' -t candidates < <(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"
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Collapse to top-level (first path component) and make unique.
|
|
||||||
# This prevents listing every file under node_modules/ or build/.
|
|
||||||
declare -A _seen
|
|
||||||
tops=()
|
|
||||||
for c in "${candidates[@]}"; do
|
|
||||||
# remove trailing slash if present
|
|
||||||
c="${c%/}"
|
|
||||||
top="${c%%/*}"
|
|
||||||
[ -z "$top" ] && continue
|
|
||||||
if [ -z "${_seen[$top]:-}" ]; then
|
|
||||||
_seen[$top]=1
|
|
||||||
tops+=("$top")
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
if [ ${#tops[@]} -eq 0 ]; then
|
|
||||||
echo "No top-level ignored/untracked entries found in $repo_root"
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Filter top-level entries by provided patterns (if any)
|
|
||||||
if [ ${#PATTERNS[@]} -gt 0 ]; then
|
|
||||||
filtered=()
|
|
||||||
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
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Present selection
|
|
||||||
if command -v fzf >/dev/null 2>&1 && [ "$USE_FZF" -eq 1 ]; then
|
|
||||||
# Show preview of the source file (if text) and allow multi-select
|
|
||||||
selected=$(printf "%s\n" "${filtered[@]}" | fzf --multi --height=40% --border --prompt="Select files to link: " --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." && exit 0
|
|
||||||
fi
|
|
||||||
# Convert to array
|
|
||||||
IFS=$'\n' read -r -d '' -a chosen < <(printf "%s\n" "$selected" && printf '\0')
|
|
||||||
else
|
|
||||||
# Non-interactive: choose all
|
|
||||||
chosen=("${filtered[@]}")
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Worktree destination is current working directory
|
|
||||||
worktree_root=$(pwd)
|
|
||||||
|
|
||||||
echo "Repository root: $repo_root"
|
|
||||||
echo "Worktree root : $worktree_root"
|
|
||||||
|
|
||||||
# Create symlinks
|
|
||||||
created=()
|
|
||||||
skipped=()
|
|
||||||
errors=()
|
|
||||||
|
|
||||||
for rel in "${chosen[@]}"; do
|
|
||||||
# Trim trailing newlines/spaces
|
|
||||||
rel=${rel%%$'\n'}
|
|
||||||
src="$repo_root/$rel"
|
|
||||||
dst="$worktree_root/$rel"
|
|
||||||
|
|
||||||
if [ ! -e "$src" ]; then
|
|
||||||
errors+=("$rel (source missing)")
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -L "$dst" ]; then
|
|
||||||
# Already a symlink
|
|
||||||
echo "Skipping $rel (already symlink)"
|
|
||||||
skipped+=("$rel")
|
|
||||||
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
|
|
||||||
echo "DRY RUN: ln -s '$src' '$dst'"
|
|
||||||
else
|
|
||||||
if ln -s "$src" "$dst"; then
|
|
||||||
echo "Linked: $rel"
|
|
||||||
created+=("$rel")
|
|
||||||
else
|
|
||||||
echo "Failed to link: $rel" >&2
|
|
||||||
errors+=("$rel (link failed)")
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
# Summary
|
|
||||||
echo
|
|
||||||
echo "Summary:"
|
|
||||||
echo " Linked: ${#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[@]}"
|
|
||||||
|
|
||||||
exit 0
|
|
||||||
|
|
@ -211,6 +211,7 @@
|
||||||
"2c:cf:67:6a:45:47,HOMEASSISTANT,10.12.14.22"
|
"2c:cf:67:6a:45:47,HOMEASSISTANT,10.12.14.22"
|
||||||
"2a:d0:ec:fa:b9:7e,PIXEL-6,10.12.14.31"
|
"2a:d0:ec:fa:b9:7e,PIXEL-6,10.12.14.31"
|
||||||
"a8:29:48:94:23:dd,TL-SG1428PE,10.12.16.2"
|
"a8:29:48:94:23:dd,TL-SG1428PE,10.12.16.2"
|
||||||
|
"00:23:a4:0b:3b:be,TMREM00004335,10.12.14.181"
|
||||||
];
|
];
|
||||||
|
|
||||||
enable-ra = lib.mkIf config.networking.enableIPv6 true;
|
enable-ra = lib.mkIf config.networking.enableIPv6 true;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue