From 56b5bdb5acaa080ad50b4be3e8586d20d0c89524 Mon Sep 17 00:00:00 2001 From: "Kenneth Benzie (Benie)" Date: Mon, 10 Aug 2026 12:42:53 +0100 Subject: [PATCH] Improve git worktree session workflow --- hooks/session-create.sh | 6 ++++++ install.sh | 2 ++ layouts/session-project | 35 +++++++++++++++++++++++++++++++++++ project.sh | 16 +++++++++++++++- session.sh | 7 +++++-- theme.tmux | 5 ++++- 6 files changed, 67 insertions(+), 4 deletions(-) create mode 100755 layouts/session-project diff --git a/hooks/session-create.sh b/hooks/session-create.sh index 23ac58a..0febc11 100755 --- a/hooks/session-create.sh +++ b/hooks/session-create.sh @@ -2,8 +2,14 @@ session_name=$(tmux display-message -p '#S') session_layout=~/.local/share/tmux/layouts/session-$session_name +# Set by `session -p `, which names the session after the project but +# can't be relied on for the lookup above since tmux rewrites `.` in names. +session_project=$(tmux show-environment -t "$session_name" TMUX_PROJECT 2>/dev/null | + sed -n 's/^TMUX_PROJECT=//p') if [ -f $session_layout ]; then $session_layout +elif [ -n "$session_project" ]; then + ~/.local/share/tmux/layouts/session-project "$session_project" elif [ "$session_name" != "agent" ]; then tmux rename-window home fi diff --git a/install.sh b/install.sh index 1676286..6992a55 100755 --- a/install.sh +++ b/install.sh @@ -31,6 +31,8 @@ symlink $HOME/.config/tmux/layouts/session-infra \ $HOME/.local/share/tmux/layouts/session-infra symlink $HOME/.config/tmux/layouts/session-main \ $HOME/.local/share/tmux/layouts/session-main +symlink $HOME/.config/tmux/layouts/session-project \ + $HOME/.local/share/tmux/layouts/session-project symlink $HOME/.config/tmux/layouts/session-visor \ $HOME/.local/share/tmux/layouts/session-visor symlink $HOME/.config/tmux/layouts/session-work \ diff --git a/layouts/session-project b/layouts/session-project new file mode 100755 index 0000000..fb98bf6 --- /dev/null +++ b/layouts/session-project @@ -0,0 +1,35 @@ +#!/usr/bin/env zsh + +# usage: session-project +# +# Layout for a session created by `session -p `, where is a +# path in ~/Projects with the prefix removed. The session-created hook passes +# the project along, taking it from the session environment. + +project=$1 +repo=$HOME/Projects/$project + +tmux rename-window home +$(dirname $0)/window-auto + +# Without a project there is nothing but the home window, which is all a +# session created directly from this layout's name can get. +if [[ -z "$project" ]] || ! git -C "$repo" rev-parse --git-dir &>/dev/null; then + exit +fi + +# The remote's default branch, falling back to whatever HEAD points at for +# repositories without an origin/HEAD, e.g. those with no remote at all. +branch=$(git -C "$repo" symbolic-ref --short refs/remotes/origin/HEAD 2>/dev/null) +branch=${branch#origin/} +if [[ -z "$branch" ]]; then + branch=$(git -C "$repo" symbolic-ref --short HEAD 2>/dev/null) +fi +if [[ -z "$branch" ]]; then + # Detached HEAD, name the window after the commit it sits on. + branch=$(git -C "$repo" rev-parse --short HEAD 2>/dev/null) +fi + +tmux new-window -c "$repo" +tmux rename-window "$branch" +$(dirname $0)/window-auto diff --git a/project.sh b/project.sh index a691885..937efa1 100755 --- a/project.sh +++ b/project.sh @@ -57,7 +57,21 @@ if [[ "${1:-}" == --pick ]]; then ) || exit 0 [ -n "$project" ] || exit 0 - tmux new-window -n "$project" -c "$projects_dir/$project" + # Inside a session for this same project, e.g. one created by `session -p`, + # the slug only repeats the session name, so name the window after the branch + # checked out there instead — a worktree's whole identity. Everything up to + # the `@` is the clone the worktrees hang off, and tmux rewrote `.` and `:` in + # the session name, so match both before comparing. + window_name=$project + session=$(tmux display -p '#S') + root=${project%%@*} + if [ "${root//[.:]/_}" = "$session" ]; then + branch=$(git -C "$projects_dir/$project" symbolic-ref --short HEAD 2>/dev/null || + git -C "$projects_dir/$project" rev-parse --short HEAD 2>/dev/null || true) + window_name=${branch:-$project} + fi + + tmux new-window -n "$window_name" -c "$projects_dir/$project" ~/.local/share/tmux/layouts/window-auto exit fi diff --git a/session.sh b/session.sh index 47f3bb1..e21f54e 100755 --- a/session.sh +++ b/session.sh @@ -4,9 +4,12 @@ set -e sessions=() -# Get list of predefined session layouts +# Get list of predefined session layouts, skipping session-project since it is +# parameterized by `session -p ` rather than a session of its own for item in $HOME/.local/share/tmux/layouts/session-*; do - sessions+=(${item#$HOME/.local/share/tmux/layouts/session-}) + layout=${item#$HOME/.local/share/tmux/layouts/session-} + [ "$layout" = project ] && continue + sessions+=($layout) done # Get list of existing sessions diff --git a/theme.tmux b/theme.tmux index 0f3edc1..b6322bc 100644 --- a/theme.tmux +++ b/theme.tmux @@ -3,7 +3,10 @@ if -b '[ "$WSL_DISTRO_NAME" != "" ]' \ 'setw -g status-left " #(hostname)|#{WSL_DISTRO_NAME}|#{session_name}"' \ 'setw -g status-left " #(hostname -s)|#{session_name}"' setw -g status-left-style fg=colour240,bg=colour233 -setw -g status-left-length 32 +# Only a maximum, and the component is never padded out to it, so leave room +# for the hostname beside a full session name: project sessions are named after +# a path, e.g. modularml/modular@benie/some-branch. +setw -g status-left-length 100 # Centre status style setw -g status-style fg=colour240,bg=colour233