From 8ad1784a8209d6345486a4c4330205e9f82ae77e Mon Sep 17 00:00:00 2001 From: Harivansh Rathi Date: Mon, 30 Mar 2026 16:37:46 +0000 Subject: [PATCH 1/2] Add netty worktree helpers Add netty-specific worktree helper commands and a zsh wrapper that creates sibling worktrees and cds into them. Also split script packaging so Darwin keeps the existing wt integration while netty gets git-worktree-based helpers. Co-authored-by: Codex --- home/netty-worktree.nix | 23 +++++++++ home/netty.nix | 4 +- home/scripts.nix | 14 +++--- home/zsh.nix | 103 +++++++++++++++++++++------------------- scripts/default.nix | 47 +++++++++++------- scripts/wt-create.sh | 29 +++++++++++ scripts/wt-path.sh | 26 ++++++++++ 7 files changed, 172 insertions(+), 74 deletions(-) create mode 100644 home/netty-worktree.nix create mode 100644 scripts/wt-create.sh create mode 100644 scripts/wt-path.sh diff --git a/home/netty-worktree.nix b/home/netty-worktree.nix new file mode 100644 index 0000000..2b97894 --- /dev/null +++ b/home/netty-worktree.nix @@ -0,0 +1,23 @@ +{ + config, + lib, + pkgs, + ... +}: let + customScripts = import ../scripts {inherit config lib pkgs;}; +in { + home.packages = builtins.attrValues customScripts.nettyPackages; + + programs.zsh.initContent = lib.mkAfter '' + wtc() { + if [[ $# -ne 1 ]]; then + printf 'usage: wtc \n' >&2 + return 1 + fi + + local worktree_path + worktree_path=$(command wt-create "$1") || return + cd -- "$worktree_path" || return + } + ''; +} diff --git a/home/netty.nix b/home/netty.nix index 6da8357..066b82e 100644 --- a/home/netty.nix +++ b/home/netty.nix @@ -1,6 +1,6 @@ -{ ... }: -{ +{...}: { imports = [ ./common.nix + ./netty-worktree.nix ]; } diff --git a/home/scripts.nix b/home/scripts.nix index d0b8170..a62e465 100644 --- a/home/scripts.nix +++ b/home/scripts.nix @@ -3,14 +3,14 @@ lib, pkgs, ... -}: -let - customScripts = import ../scripts { inherit config lib pkgs; }; -in -{ - home.packages = builtins.attrValues customScripts.packages; +}: let + customScripts = import ../scripts {inherit config lib pkgs;}; +in { + home.packages = + builtins.attrValues customScripts.commonPackages + ++ lib.optionals pkgs.stdenv.isDarwin (builtins.attrValues customScripts.darwinPackages); - home.activation.initializeThemeState = lib.hm.dag.entryAfter [ "writeBoundary" ] '' + home.activation.initializeThemeState = lib.hm.dag.entryAfter ["writeBoundary"] '' mkdir -p "${customScripts.theme.paths.stateDir}" "${customScripts.theme.paths.fzfDir}" "${customScripts.theme.paths.ghosttyDir}" "${customScripts.theme.paths.tmuxDir}" if [[ -f "${customScripts.theme.paths.stateFile}" ]]; then diff --git a/home/zsh.nix b/home/zsh.nix index 3279601..be01baa 100644 --- a/home/zsh.nix +++ b/home/zsh.nix @@ -3,15 +3,14 @@ lib, pkgs, ... -}: -{ +}: { home.file.".oh-my-zsh/custom/themes/agnoster.zsh-theme".source = ../config/agnoster.zsh-theme; - home.activation.ensureOhMyZshCache = lib.hm.dag.entryAfter [ "writeBoundary" ] '' + home.activation.ensureOhMyZshCache = lib.hm.dag.entryAfter ["writeBoundary"] '' mkdir -p "${config.xdg.cacheHome}/oh-my-zsh" ''; - home.packages = [ pkgs.oh-my-zsh ]; + home.packages = [pkgs.oh-my-zsh]; programs.zsh = { enable = true; @@ -22,40 +21,42 @@ autosuggestion.enable = true; syntaxHighlighting.enable = true; - shellAliases = { - co = "codex --dangerously-bypass-approvals-and-sandbox"; - ca = "cursor-agent"; - cc = "claude"; - ch = "claude-handoff"; - cl = "clear"; - gc = "git commit"; - gd = "git diff"; - gk = "git checkout"; - gp = "git push"; - gpo = "git pull origin"; - gs = "git status"; - ld = "lumen diff"; - lg = "lazygit"; - nim = "nvim ."; - } - // lib.optionalAttrs pkgs.stdenv.isDarwin { - tailscale = "/Applications/Tailscale.app/Contents/MacOS/Tailscale"; - }; + shellAliases = + { + co = "codex --dangerously-bypass-approvals-and-sandbox"; + ca = "cursor-agent"; + cc = "claude"; + ch = "claude-handoff"; + cl = "clear"; + gc = "git commit"; + gd = "git diff"; + gk = "git checkout"; + gp = "git push"; + gpo = "git pull origin"; + gs = "git status"; + ld = "lumen diff"; + lg = "lazygit"; + nim = "nvim ."; + } + // lib.optionalAttrs pkgs.stdenv.isDarwin { + tailscale = "/Applications/Tailscale.app/Contents/MacOS/Tailscale"; + }; - envExtra = '' - if [[ -f "$HOME/.cargo/env" ]]; then - . "$HOME/.cargo/env" - fi - export NODE_NO_WARNINGS=1 - '' - + lib.optionalString pkgs.stdenv.isDarwin '' - # Ghostty shell integration expects a resource directory; the Nix app - # bundle lives in the store instead of /Applications. - export GHOSTTY_RESOURCES_DIR="${pkgs.ghostty-bin}/Applications/Ghostty.app/Contents/Resources/ghostty" - '' - + '' - export MANPAGER="nvim +Man!" - ''; + envExtra = + '' + if [[ -f "$HOME/.cargo/env" ]]; then + . "$HOME/.cargo/env" + fi + export NODE_NO_WARNINGS=1 + '' + + lib.optionalString pkgs.stdenv.isDarwin '' + # Ghostty shell integration expects a resource directory; the Nix app + # bundle lives in the store instead of /Applications. + export GHOSTTY_RESOURCES_DIR="${pkgs.ghostty-bin}/Applications/Ghostty.app/Contents/Resources/ghostty" + '' + + '' + export MANPAGER="nvim +Man!" + ''; initContent = lib.mkMerge [ (lib.mkOrder 550 '' @@ -86,7 +87,9 @@ export BUN_INSTALL="$HOME/.bun" export PNPM_HOME="${ - if pkgs.stdenv.isDarwin then "$HOME/Library/pnpm" else "${config.xdg.dataHome}/pnpm" + if pkgs.stdenv.isDarwin + then "$HOME/Library/pnpm" + else "${config.xdg.dataHome}/pnpm" }" bindkey -v typeset -U path PATH @@ -103,9 +106,9 @@ "/run/current-system/sw/bin" "/nix/var/nix/profiles/default/bin" ${lib.optionalString pkgs.stdenv.isDarwin '' - "/opt/homebrew/bin" - "/opt/homebrew/sbin" - ''} + "/opt/homebrew/bin" + "/opt/homebrew/sbin" + ''} $path ) @@ -249,15 +252,17 @@ _codex_apply_highlight_styles - if command -v wt >/dev/null 2>&1; then - eval "$(command wt config shell init zsh)" + ${lib.optionalString pkgs.stdenv.isDarwin '' + if command -v wt >/dev/null 2>&1; then + eval "$(command wt config shell init zsh)" - # `wt` changes directories by sourcing directives into the current shell, - # so wrappers around it must stay shell functions instead of scripts. - wtc() { - wt switch --create --base @ "$@" - } - fi + # `wt` changes directories by sourcing directives into the current shell, + # so wrappers around it must stay shell functions instead of scripts. + wtc() { + wt switch --create --base @ "$@" + } + fi + ''} '') (lib.mkAfter '' diff --git a/scripts/default.nix b/scripts/default.nix index f32a0f6..a1fcb55 100644 --- a/scripts/default.nix +++ b/scripts/default.nix @@ -2,22 +2,20 @@ config, lib, pkgs, -}: -let - theme = import ../lib/theme.nix { inherit config; }; +}: let + theme = import ../lib/theme.nix {inherit config;}; tmuxConfigs = { dark = pkgs.writeText "tmux-theme-dark.conf" (theme.renderTmux "dark"); light = pkgs.writeText "tmux-theme-light.conf" (theme.renderTmux "light"); }; - mkScript = - { - file, - name, - runtimeInputs ? [ ], - replacements ? { }, - }: + mkScript = { + file, + name, + runtimeInputs ? [], + replacements ? {}, + }: pkgs.writeShellApplication { inherit name runtimeInputs; text = lib.replaceStrings (builtins.attrNames replacements) (builtins.attrValues replacements) ( @@ -25,11 +23,11 @@ let ); }; - packages = { + commonPackages = { ga = mkScript { name = "ga"; file = ./ga.sh; - runtimeInputs = with pkgs; [ git ]; + runtimeInputs = with pkgs; [git]; }; ghpr = mkScript { @@ -73,7 +71,7 @@ let ni = mkScript { name = "ni"; file = ./ni.sh; - runtimeInputs = with pkgs; [ nix ]; + runtimeInputs = with pkgs; [nix]; }; theme = mkScript { @@ -104,13 +102,30 @@ let "@TMUX_CONFIG@" = "${config.xdg.configHome}/tmux/tmux.conf"; }; }; + }; + darwinPackages = { wtc = mkScript { name = "wtc"; file = ./wtc.sh; }; }; -in -{ - inherit packages theme tmuxConfigs; + + nettyPackages = { + wt-create = mkScript { + name = "wt-create"; + file = ./wt-create.sh; + runtimeInputs = with pkgs; [coreutils git gnused]; + }; + + wt-path = mkScript { + name = "wt-path"; + file = ./wt-path.sh; + runtimeInputs = with pkgs; [coreutils git gnused]; + }; + }; +in { + inherit commonPackages darwinPackages nettyPackages theme tmuxConfigs; + + packages = commonPackages; } diff --git a/scripts/wt-create.sh b/scripts/wt-create.sh new file mode 100644 index 0000000..278a91f --- /dev/null +++ b/scripts/wt-create.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash + +set -euo pipefail + +if [[ $# -ne 1 ]]; then + printf 'usage: wt-create \n' >&2 + exit 1 +fi + +branch_name=$1 +repo_root=$(git rev-parse --show-toplevel 2>/dev/null) || { + printf 'wt-create: not inside a git repository\n' >&2 + exit 1 +} + +target_path=$(wt-path "$branch_name") + +if [[ -e "$target_path" ]]; then + printf 'wt-create: path already exists: %s\n' "$target_path" >&2 + exit 1 +fi + +if git -C "$repo_root" show-ref --verify --quiet "refs/heads/$branch_name"; then + git -C "$repo_root" worktree add -- "$target_path" "$branch_name" 1>&2 +else + git -C "$repo_root" worktree add -b "$branch_name" -- "$target_path" HEAD 1>&2 +fi + +printf '%s\n' "$target_path" diff --git a/scripts/wt-path.sh b/scripts/wt-path.sh new file mode 100644 index 0000000..46e4b62 --- /dev/null +++ b/scripts/wt-path.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash + +set -euo pipefail + +if [[ $# -ne 1 ]]; then + printf 'usage: wt-path \n' >&2 + exit 1 +fi + +repo_root=$(git rev-parse --show-toplevel 2>/dev/null) || { + printf 'wt-path: not inside a git repository\n' >&2 + exit 1 +} + +worktree_name=$1 +clean_name=$(printf '%s' "$worktree_name" | sed -E 's#[^[:alnum:]._-]+#-#g; s#-+#-#g; s#(^[.-]+|[.-]+$)##g') + +if [[ -z "$clean_name" ]]; then + printf 'wt-path: %s does not produce a usable path name\n' "$worktree_name" >&2 + exit 1 +fi + +repo_parent=$(dirname "$repo_root") +repo_name=$(basename "$repo_root") + +printf '%s/%s-%s\n' "$repo_parent" "$repo_name" "$clean_name" From 9f1f4f04dd71dbe754d7e601dfa93979f72875d5 Mon Sep 17 00:00:00 2001 From: Harivansh Rathi Date: Mon, 30 Mar 2026 17:26:46 +0000 Subject: [PATCH 2/2] Fix worktree path resolution inside linked worktrees Resolve the path anchor from the repository common git dir so running wtc from inside an existing worktree still names sibling worktrees from the main checkout root. Co-authored-by: Codex --- scripts/wt-path.sh | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/scripts/wt-path.sh b/scripts/wt-path.sh index 46e4b62..9d8abb9 100644 --- a/scripts/wt-path.sh +++ b/scripts/wt-path.sh @@ -7,11 +7,16 @@ if [[ $# -ne 1 ]]; then exit 1 fi -repo_root=$(git rev-parse --show-toplevel 2>/dev/null) || { +common_git_dir=$(git rev-parse --path-format=absolute --git-common-dir 2>/dev/null) || { printf 'wt-path: not inside a git repository\n' >&2 exit 1 } +repo_root=$(cd "${common_git_dir}/.." && pwd -P) || { + printf 'wt-path: failed to resolve repository root\n' >&2 + exit 1 +} + worktree_name=$1 clean_name=$(printf '%s' "$worktree_name" | sed -E 's#[^[:alnum:]._-]+#-#g; s#-+#-#g; s#(^[.-]+|[.-]+$)##g')