Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 24 additions & 4 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
tmux-version: ['3.2', '3.3', '3.4']
# README.md supports tmux 3.0+, so keep the compatibility floor in CI.
tmux-version: ['3.0', '3.1', '3.2', '3.3', '3.4']
fail-fast: false

steps:
Expand Down Expand Up @@ -51,9 +52,7 @@ jobs:
run: sudo apt-get install -y shellcheck

- name: Lint scripts
run: |
shellcheck scripts/*.sh || true
echo "Shellcheck completed (warnings allowed)"
run: shellcheck scripts/*.sh

# lib/ is vendored from KakkoiDev/tmux-toolkit via git subtree. Editing it
# in place here is how five plugins accumulated 26 duplicated helpers in
Expand All @@ -73,6 +72,27 @@ jobs:
fi
echo "lib/ matches tmux-toolkit $(cat lib/VERSION)"

bash-3-2:
# macOS still ships /bin/bash 3.2, the minimum documented in README.md.
runs-on: macos-15
steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Install dependencies
run: brew install bats-core expect tmux

- name: Verify compatibility shell
run: |
/bin/bash --version
/bin/bash -c '[[ ${BASH_VERSINFO[0]}.${BASH_VERSINFO[1]} == 3.2 ]]'

- name: Run unit tests with Bash 3.2
run: make test-unit SHELL=/bin/bash

- name: Run integration tests with Bash 3.2
run: make test-integration SHELL=/bin/bash

smoke:
runs-on: ubuntu-latest
steps:
Expand Down
1 change: 1 addition & 0 deletions scripts/filter.sh
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ matches_filter() {
# Use bash extended pattern matching
# * becomes * (works in case)
# ? becomes ? (works in case)
# shellcheck disable=SC2254 # The user-supplied glob is intentional here.
case "$lower_string" in
$lower_pattern) return 0 ;;
*) return 1 ;;
Expand Down
5 changes: 4 additions & 1 deletion scripts/helpers.sh
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
# VERSION
# ==============================================================================

# shellcheck disable=SC2034 # Public variable consumed by worktree_manager.sh.
TMUX_WORKTREE_VERSION="0.1.0"

# Determine plugin directory (works when sourced or executed)
Expand All @@ -19,6 +20,7 @@ else
PLUGIN_DIR="$(cd "$(dirname "$0")/.." && pwd)"
fi

# shellcheck disable=SC2034 # Public variable used by plugin loaders and tests.
SCRIPTS_DIR="$PLUGIN_DIR/scripts"

# ==============================================================================
Expand All @@ -34,7 +36,7 @@ SCRIPTS_DIR="$PLUGIN_DIR/scripts"
# `if [ -n "$TMUX_SOCKET" ]` branches into one. tk_tmux prepends -L when it is
# set, so every call site becomes a plain `tk_tmux ...` with no fork.

# shellcheck source=../lib/toolkit.sh
# shellcheck disable=SC1091 # Runtime path is derived from the plugin checkout.
source "$PLUGIN_DIR/lib/toolkit.sh"
tk_require_version 0.2.0
tk_init worktree "${WORKTREE_BASE:-$HOME/.tmux-worktree}"
Expand Down Expand Up @@ -268,6 +270,7 @@ load_config() {

if _is_cache_valid "$cache_file"; then
# Fast path: read from cache file
# shellcheck disable=SC1090 # The generated cache path is intentionally dynamic.
source "$cache_file"
debug_log "load_config: from cache=$cache_file"
else
Expand Down
31 changes: 15 additions & 16 deletions scripts/worktree_manager.sh
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,11 @@ else
fi

# Source helpers and load config (only if not already loaded in test context)
# shellcheck disable=SC1091 # Runtime path is derived from this script's location.
source "$SCRIPT_DIR/helpers.sh"
# shellcheck disable=SC1091 # Runtime path is derived from this script's location.
source "$SCRIPT_DIR/filter.sh"
# shellcheck source=../lib/menu.sh
# shellcheck disable=SC1091 # Runtime path is derived from the plugin checkout.
source "${PLUGIN_DIR:-$SCRIPT_DIR/..}/lib/menu.sh"
# Skip load_config if WORKTREE_BASE is already set to a temp path (test mode)
if [[ ! "$WORKTREE_BASE" == /tmp/* ]]; then
Expand Down Expand Up @@ -81,13 +83,13 @@ fetch_remote_branches() {
debug_log "Starting git fetch in $(pwd) with timeout ${timeout_seconds}s"

# Build fetch command (--prune only when explicitly enabled)
local fetch_args="--all"
local fetch_args=(--all)
if [ "${FETCH_PRUNE:-off}" = "on" ]; then
fetch_args="--all --prune"
fetch_args+=(--prune)
fi

# Run git fetch with timeout, capture stderr
if run_with_timeout "$timeout_seconds" git fetch $fetch_args 2>"$error_file"; then
if run_with_timeout "$timeout_seconds" git fetch "${fetch_args[@]}" 2>"$error_file"; then
debug_log "Fetch completed successfully"
tmux display-message "Remote branches fetched successfully"
rm -f "$error_file"
Expand All @@ -96,7 +98,7 @@ fetch_remote_branches() {
local exit_code=$?
local error_msg
error_msg=$(head -1 "$error_file" 2>/dev/null | cut -c1-80)
error_log "fetch_remote_branches: cwd=$(pwd) args=$fetch_args exit=$exit_code err=$(cat "$error_file" 2>/dev/null)"
error_log "fetch_remote_branches: cwd=$(pwd) args=${fetch_args[*]} exit=$exit_code err=$(cat "$error_file" 2>/dev/null)"

# Show first line of error (truncated) or generic message
if [ -n "$error_msg" ]; then
Expand Down Expand Up @@ -523,11 +525,6 @@ _add_nav_items() {
local extra2=${6:-}
local script_path="$SCRIPT_DIR/worktree_manager.sh"

local filter_args=""
if [ -n "$filter" ]; then
filter_args="'$filter'"
fi

# Build the sub-args list for tk_menu_cmd.
# We must pass each positional arg separately so quoting works.
local _nav_args=()
Expand All @@ -538,7 +535,8 @@ _add_nav_items() {
[ -n "$filter" ] && _nav_args+=("$filter")
[ -n "$extra1" ] && _nav_args+=("$extra1")
[ -n "$extra2" ] && _nav_args+=("$extra2")
local prev_cmd="display-message 'Loading...' ; $(tk_menu_cmd "${_nav_args[@]}")"
local prev_cmd
prev_cmd="display-message 'Loading...' ; $(tk_menu_cmd "${_nav_args[@]}")"
tk_menu_item "◀ Previous" "$KEY_PREV" "$prev_cmd"
fi

Expand All @@ -548,7 +546,8 @@ _add_nav_items() {
[ -n "$filter" ] && _nav_args+=("$filter")
[ -n "$extra1" ] && _nav_args+=("$extra1")
[ -n "$extra2" ] && _nav_args+=("$extra2")
local next_cmd="display-message 'Loading...' ; $(tk_menu_cmd "${_nav_args[@]}")"
local next_cmd
next_cmd="display-message 'Loading...' ; $(tk_menu_cmd "${_nav_args[@]}")"
tk_menu_item "Next ▶" "$KEY_NEXT" "$next_cmd"
fi

Expand Down Expand Up @@ -627,7 +626,7 @@ show_worktree_menu() {

# Clear filter option (only when filter active, preserves sort_recent)
if [ -n "$filter" ]; then
tk_menu_item "Clear filter" "$KEY_CLEAR_FILTER" "$(tk_menu_cmd "$script_path" show_worktree_menu 1 '' $sort_recent)"
tk_menu_item "Clear filter" "$KEY_CLEAR_FILTER" "$(tk_menu_cmd "$script_path" show_worktree_menu 1 '' "$sort_recent")"
fi

# Parse TSV worktree items
Expand Down Expand Up @@ -847,7 +846,7 @@ adopt_current_session() {
# Adoption must derive the project from this session's own directory, never
# from a TMUX_WORKTREE_PROJECT inherited by the hook/sweep child process
# (it leaks across unrelated sessions and mis-stamps every adopted name).
project=$(TMUX_WORKTREE_PROJECT= get_project_name)
project=$(TMUX_WORKTREE_PROJECT='' get_project_name)
[ -z "$project" ] && return 0

# Sanitized "<project>-" prefix (matches whatever get_session_name will emit,
Expand Down Expand Up @@ -1086,7 +1085,7 @@ show_add_worktree_menu() {

# Clear filter option (only when filter active)
if [ -n "$filter" ]; then
tk_menu_item "Clear filter" "$KEY_CLEAR_FILTER" "$(tk_menu_cmd "$script_path" show_add_worktree_menu 1 '' $include_remotes)"
tk_menu_item "Clear filter" "$KEY_CLEAR_FILTER" "$(tk_menu_cmd "$script_path" show_add_worktree_menu 1 '' "$include_remotes")"
fi

# Parse TSV branch items
Expand Down Expand Up @@ -1389,7 +1388,7 @@ show_options_menu() {
# Trim whitespace from each entry.
local _i
for _i in "${!_age_arr[@]}"; do
_age_arr[$_i]="${_age_arr[$_i]// /}"
_age_arr[_i]="${_age_arr[_i]// /}"
done
if [ "${#_age_arr[@]}" -eq 0 ]; then
_age_arr=("7" "30" "90")
Expand Down
6 changes: 3 additions & 3 deletions tests/integration/test_worktree_lifecycle.bats
Original file line number Diff line number Diff line change
Expand Up @@ -92,15 +92,15 @@ teardown() {
# WORKTREE DATA TESTS
# ==============================================================================

@test "get_worktree_data returns menu format" {
@test "get_worktree_data returns TSV data" {
local wt_dir="$WORKTREE_BASE/$(get_project_name)/feature-one"
mkdir -p "$(dirname "$wt_dir")"
git worktree add -q "$wt_dir" feature-one

run get_worktree_data 1 ""
assert_success
assert_contains "$output" 'feature-one'
assert_contains "$output" 'run-shell'
assert_contains "$output" $'feature-one\tfeature-one\t'
[[ "$output" != *run-shell* ]]

git worktree remove --force "$wt_dir"
}
Expand Down
Loading