* Fix sidebar branch refresh after checkout * Fix bash PR probe not refreshing on checkout (PR review feedback) When HEAD changes (e.g. git checkout), the bash integration now resets _CMUX_PR_LAST_RUN=0 so the PR probe is forced to re-run immediately. This matches the zsh integration which already sets _CMUX_PR_FORCE=1 on HEAD change. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
1408cbb68c
commit
6d0c90c8c8
3 changed files with 186 additions and 50 deletions
|
|
@ -41,6 +41,9 @@ _CMUX_GIT_LAST_PWD="${_CMUX_GIT_LAST_PWD:-}"
|
|||
_CMUX_GIT_LAST_RUN="${_CMUX_GIT_LAST_RUN:-0}"
|
||||
_CMUX_GIT_JOB_PID="${_CMUX_GIT_JOB_PID:-}"
|
||||
_CMUX_GIT_JOB_STARTED_AT="${_CMUX_GIT_JOB_STARTED_AT:-0}"
|
||||
_CMUX_GIT_HEAD_LAST_PWD="${_CMUX_GIT_HEAD_LAST_PWD:-}"
|
||||
_CMUX_GIT_HEAD_PATH="${_CMUX_GIT_HEAD_PATH:-}"
|
||||
_CMUX_GIT_HEAD_SIGNATURE="${_CMUX_GIT_HEAD_SIGNATURE:-}"
|
||||
_CMUX_PR_LAST_PWD="${_CMUX_PR_LAST_PWD:-}"
|
||||
_CMUX_PR_LAST_RUN="${_CMUX_PR_LAST_RUN:-0}"
|
||||
_CMUX_PR_JOB_PID="${_CMUX_PR_JOB_PID:-}"
|
||||
|
|
@ -51,6 +54,41 @@ _CMUX_PORTS_LAST_RUN="${_CMUX_PORTS_LAST_RUN:-0}"
|
|||
_CMUX_TTY_NAME="${_CMUX_TTY_NAME:-}"
|
||||
_CMUX_TTY_REPORTED="${_CMUX_TTY_REPORTED:-0}"
|
||||
|
||||
_cmux_git_resolve_head_path() {
|
||||
# Resolve the HEAD file path without invoking git (fast; works for worktrees).
|
||||
local dir="$PWD"
|
||||
while :; do
|
||||
if [[ -d "$dir/.git" ]]; then
|
||||
printf '%s\n' "$dir/.git/HEAD"
|
||||
return 0
|
||||
fi
|
||||
if [[ -f "$dir/.git" ]]; then
|
||||
local line gitdir
|
||||
IFS= read -r line < "$dir/.git" || line=""
|
||||
if [[ "$line" == gitdir:* ]]; then
|
||||
gitdir="${line#gitdir:}"
|
||||
gitdir="${gitdir## }"
|
||||
gitdir="${gitdir%% }"
|
||||
[[ -n "$gitdir" ]] || return 1
|
||||
[[ "$gitdir" != /* ]] && gitdir="$dir/$gitdir"
|
||||
printf '%s\n' "$gitdir/HEAD"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
[[ "$dir" == "/" || -z "$dir" ]] && break
|
||||
dir="$(dirname "$dir")"
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
_cmux_git_head_signature() {
|
||||
local head_path="$1"
|
||||
[[ -n "$head_path" && -r "$head_path" ]] || return 1
|
||||
local line
|
||||
IFS= read -r line < "$head_path" || return 1
|
||||
printf '%s\n' "$line"
|
||||
}
|
||||
|
||||
_cmux_report_tty_once() {
|
||||
# Send the TTY name to the app once per session so the batched port scanner
|
||||
# knows which TTY belongs to this panel.
|
||||
|
|
@ -126,12 +164,31 @@ _cmux_prompt_command() {
|
|||
} >/dev/null 2>&1 &
|
||||
fi
|
||||
|
||||
# Branch can change via aliases/tools while an older probe is still in flight.
|
||||
# Track .git/HEAD content so we can restart stale probes immediately.
|
||||
local git_head_changed=0
|
||||
if [[ "$pwd" != "$_CMUX_GIT_HEAD_LAST_PWD" ]]; then
|
||||
_CMUX_GIT_HEAD_LAST_PWD="$pwd"
|
||||
_CMUX_GIT_HEAD_PATH="$(_cmux_git_resolve_head_path 2>/dev/null || true)"
|
||||
_CMUX_GIT_HEAD_SIGNATURE=""
|
||||
fi
|
||||
if [[ -n "$_CMUX_GIT_HEAD_PATH" ]]; then
|
||||
local head_signature
|
||||
head_signature="$(_cmux_git_head_signature "$_CMUX_GIT_HEAD_PATH" 2>/dev/null || true)"
|
||||
if [[ -n "$head_signature" && "$head_signature" != "$_CMUX_GIT_HEAD_SIGNATURE" ]]; then
|
||||
_CMUX_GIT_HEAD_SIGNATURE="$head_signature"
|
||||
git_head_changed=1
|
||||
# Also invalidate the PR probe so it refreshes with the new branch.
|
||||
_CMUX_PR_LAST_RUN=0
|
||||
fi
|
||||
fi
|
||||
|
||||
# Git branch/dirty can change without a directory change (e.g. `git checkout`),
|
||||
# so update on every prompt (still async + de-duped by the running-job check).
|
||||
# When pwd changes (cd into a different repo), kill the old probe and start fresh
|
||||
# so the sidebar picks up the new branch immediately.
|
||||
if [[ -n "$_CMUX_GIT_JOB_PID" ]] && kill -0 "$_CMUX_GIT_JOB_PID" 2>/dev/null; then
|
||||
if [[ "$pwd" != "$_CMUX_GIT_LAST_PWD" ]]; then
|
||||
if [[ "$pwd" != "$_CMUX_GIT_LAST_PWD" || "$git_head_changed" == "1" ]]; then
|
||||
kill "$_CMUX_GIT_JOB_PID" >/dev/null 2>&1 || true
|
||||
_CMUX_GIT_JOB_PID=""
|
||||
_CMUX_GIT_JOB_STARTED_AT=0
|
||||
|
|
@ -158,16 +215,16 @@ _cmux_prompt_command() {
|
|||
fi
|
||||
|
||||
# Pull request metadata (number/state/url):
|
||||
# refresh on cwd change and periodically to avoid stale status.
|
||||
# refresh on cwd change, HEAD change, and periodically to avoid stale status.
|
||||
if [[ -n "$_CMUX_PR_JOB_PID" ]] && kill -0 "$_CMUX_PR_JOB_PID" 2>/dev/null; then
|
||||
if [[ "$pwd" != "$_CMUX_PR_LAST_PWD" ]]; then
|
||||
if [[ "$pwd" != "$_CMUX_PR_LAST_PWD" || "$git_head_changed" == "1" ]]; then
|
||||
kill "$_CMUX_PR_JOB_PID" >/dev/null 2>&1 || true
|
||||
_CMUX_PR_JOB_PID=""
|
||||
_CMUX_PR_JOB_STARTED_AT=0
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ "$pwd" != "$_CMUX_PR_LAST_PWD" ]] || (( now - _CMUX_PR_LAST_RUN >= 60 )); then
|
||||
if [[ "$pwd" != "$_CMUX_PR_LAST_PWD" || "$git_head_changed" == "1" ]] || (( now - _CMUX_PR_LAST_RUN >= 60 )); then
|
||||
if [[ -z "$_CMUX_PR_JOB_PID" ]] || ! kill -0 "$_CMUX_PR_JOB_PID" 2>/dev/null; then
|
||||
_CMUX_PR_LAST_PWD="$pwd"
|
||||
_CMUX_PR_LAST_RUN=$now
|
||||
|
|
|
|||
|
|
@ -45,9 +45,8 @@ typeset -g _CMUX_GIT_JOB_STARTED_AT=0
|
|||
typeset -g _CMUX_GIT_FORCE=0
|
||||
typeset -g _CMUX_GIT_HEAD_LAST_PWD=""
|
||||
typeset -g _CMUX_GIT_HEAD_PATH=""
|
||||
typeset -g _CMUX_GIT_HEAD_MTIME=0
|
||||
typeset -g _CMUX_GIT_HEAD_SIGNATURE=""
|
||||
typeset -g _CMUX_GIT_HEAD_WATCH_PID=""
|
||||
typeset -g _CMUX_HAVE_ZSTAT=0
|
||||
typeset -g _CMUX_PR_LAST_PWD=""
|
||||
typeset -g _CMUX_PR_LAST_RUN=0
|
||||
typeset -g _CMUX_PR_JOB_PID=""
|
||||
|
|
@ -60,19 +59,6 @@ typeset -g _CMUX_CMD_START=0
|
|||
typeset -g _CMUX_TTY_NAME=""
|
||||
typeset -g _CMUX_TTY_REPORTED=0
|
||||
|
||||
_cmux_ensure_zstat() {
|
||||
# zstat is substantially cheaper than spawning external `stat`.
|
||||
if (( _CMUX_HAVE_ZSTAT != 0 )); then
|
||||
return 0
|
||||
fi
|
||||
if zmodload -F zsh/stat b:zstat 2>/dev/null; then
|
||||
_CMUX_HAVE_ZSTAT=1
|
||||
return 0
|
||||
fi
|
||||
_CMUX_HAVE_ZSTAT=-1
|
||||
return 1
|
||||
}
|
||||
|
||||
_cmux_git_resolve_head_path() {
|
||||
# Resolve the HEAD file path without invoking git (fast; works for worktrees).
|
||||
local dir="$PWD"
|
||||
|
|
@ -100,27 +86,15 @@ _cmux_git_resolve_head_path() {
|
|||
return 1
|
||||
}
|
||||
|
||||
_cmux_git_head_mtime() {
|
||||
_cmux_git_head_signature() {
|
||||
local head_path="$1"
|
||||
[[ -n "$head_path" && -f "$head_path" ]] || { print -r -- 0; return 0; }
|
||||
|
||||
if _cmux_ensure_zstat; then
|
||||
typeset -A st
|
||||
if zstat -H st +mtime -- "$head_path" 2>/dev/null; then
|
||||
print -r -- "${st[mtime]:-0}"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
# Fallback for environments where zsh/stat isn't available.
|
||||
if command -v stat >/dev/null 2>&1; then
|
||||
local mtime
|
||||
mtime="$(stat -f %m "$head_path" 2>/dev/null || stat -c %Y "$head_path" 2>/dev/null || echo 0)"
|
||||
print -r -- "$mtime"
|
||||
[[ -n "$head_path" && -r "$head_path" ]] || return 1
|
||||
local line=""
|
||||
if IFS= read -r line < "$head_path"; then
|
||||
print -r -- "$line"
|
||||
return 0
|
||||
fi
|
||||
|
||||
print -r -- 0
|
||||
return 1
|
||||
}
|
||||
|
||||
_cmux_report_tty_once() {
|
||||
|
|
@ -184,23 +158,23 @@ _cmux_start_git_head_watch() {
|
|||
watch_head_path="$(_cmux_git_resolve_head_path 2>/dev/null || true)"
|
||||
[[ -n "$watch_head_path" ]] || return 0
|
||||
|
||||
local watch_head_mtime
|
||||
watch_head_mtime="$(_cmux_git_head_mtime "$watch_head_path" 2>/dev/null || echo 0)"
|
||||
local watch_head_signature
|
||||
watch_head_signature="$(_cmux_git_head_signature "$watch_head_path" 2>/dev/null || true)"
|
||||
|
||||
_CMUX_GIT_HEAD_LAST_PWD="$watch_pwd"
|
||||
_CMUX_GIT_HEAD_PATH="$watch_head_path"
|
||||
_CMUX_GIT_HEAD_MTIME="$watch_head_mtime"
|
||||
_CMUX_GIT_HEAD_SIGNATURE="$watch_head_signature"
|
||||
|
||||
_cmux_stop_git_head_watch
|
||||
{
|
||||
local last_mtime="$watch_head_mtime"
|
||||
local last_signature="$watch_head_signature"
|
||||
while true; do
|
||||
sleep 1
|
||||
|
||||
local mtime
|
||||
mtime="$(_cmux_git_head_mtime "$watch_head_path" 2>/dev/null || echo 0)"
|
||||
if [[ -n "$mtime" && "$mtime" != 0 && "$mtime" != "$last_mtime" ]]; then
|
||||
last_mtime="$mtime"
|
||||
local signature
|
||||
signature="$(_cmux_git_head_signature "$watch_head_path" 2>/dev/null || true)"
|
||||
if [[ -n "$signature" && "$signature" != "$last_signature" ]]; then
|
||||
last_signature="$signature"
|
||||
_cmux_report_git_branch_for_path "$watch_pwd"
|
||||
fi
|
||||
done
|
||||
|
|
@ -299,13 +273,13 @@ _cmux_precmd() {
|
|||
if [[ "$pwd" != "$_CMUX_GIT_HEAD_LAST_PWD" ]]; then
|
||||
_CMUX_GIT_HEAD_LAST_PWD="$pwd"
|
||||
_CMUX_GIT_HEAD_PATH="$(_cmux_git_resolve_head_path 2>/dev/null || true)"
|
||||
_CMUX_GIT_HEAD_MTIME=0
|
||||
_CMUX_GIT_HEAD_SIGNATURE=""
|
||||
fi
|
||||
if [[ -n "$_CMUX_GIT_HEAD_PATH" ]]; then
|
||||
local head_mtime
|
||||
head_mtime="$(_cmux_git_head_mtime "$_CMUX_GIT_HEAD_PATH" 2>/dev/null || echo 0)"
|
||||
if [[ -n "$head_mtime" && "$head_mtime" != 0 && "$head_mtime" != "$_CMUX_GIT_HEAD_MTIME" ]]; then
|
||||
_CMUX_GIT_HEAD_MTIME="$head_mtime"
|
||||
local head_signature
|
||||
head_signature="$(_cmux_git_head_signature "$_CMUX_GIT_HEAD_PATH" 2>/dev/null || true)"
|
||||
if [[ -n "$head_signature" && "$head_signature" != "$_CMUX_GIT_HEAD_SIGNATURE" ]]; then
|
||||
_CMUX_GIT_HEAD_SIGNATURE="$head_signature"
|
||||
# Treat HEAD file change like a git command — force-replace any
|
||||
# running probe so the sidebar picks up the new branch immediately.
|
||||
_CMUX_GIT_FORCE=1
|
||||
|
|
|
|||
105
tests/test_issue_666_sidebar_branch_checkout_refresh.py
Normal file
105
tests/test_issue_666_sidebar_branch_checkout_refresh.py
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Regression guard for issue #666 (sidebar branch stuck after checkout)."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def get_repo_root() -> Path:
|
||||
result = subprocess.run(
|
||||
["git", "rev-parse", "--show-toplevel"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
if result.returncode == 0:
|
||||
return Path(result.stdout.strip())
|
||||
return Path.cwd()
|
||||
|
||||
|
||||
def require(content: str, needle: str, message: str, failures: list[str]) -> None:
|
||||
if needle not in content:
|
||||
failures.append(message)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
repo_root = get_repo_root()
|
||||
zsh_path = repo_root / "Resources" / "shell-integration" / "cmux-zsh-integration.zsh"
|
||||
bash_path = repo_root / "Resources" / "shell-integration" / "cmux-bash-integration.bash"
|
||||
|
||||
required_paths = [zsh_path, bash_path]
|
||||
missing_paths = [str(path) for path in required_paths if not path.exists()]
|
||||
if missing_paths:
|
||||
print("Missing expected files:")
|
||||
for path in missing_paths:
|
||||
print(f" - {path}")
|
||||
return 1
|
||||
|
||||
zsh_content = zsh_path.read_text(encoding="utf-8")
|
||||
bash_content = bash_path.read_text(encoding="utf-8")
|
||||
|
||||
failures: list[str] = []
|
||||
|
||||
require(
|
||||
zsh_content,
|
||||
"_CMUX_GIT_HEAD_SIGNATURE",
|
||||
"zsh integration is missing git HEAD signature tracking",
|
||||
failures,
|
||||
)
|
||||
require(
|
||||
zsh_content,
|
||||
"_cmux_git_head_signature",
|
||||
"zsh integration is missing git HEAD signature helper",
|
||||
failures,
|
||||
)
|
||||
require(
|
||||
zsh_content,
|
||||
'"$head_signature" != "$_CMUX_GIT_HEAD_SIGNATURE"',
|
||||
"zsh integration no longer compares git HEAD signatures",
|
||||
failures,
|
||||
)
|
||||
require(
|
||||
zsh_content,
|
||||
"_CMUX_GIT_FORCE=1",
|
||||
"zsh integration no longer forces git probe refresh on HEAD changes",
|
||||
failures,
|
||||
)
|
||||
|
||||
require(
|
||||
bash_content,
|
||||
"_CMUX_GIT_HEAD_SIGNATURE",
|
||||
"bash integration is missing git HEAD signature tracking",
|
||||
failures,
|
||||
)
|
||||
require(
|
||||
bash_content,
|
||||
"_cmux_git_head_signature",
|
||||
"bash integration is missing git HEAD signature helper",
|
||||
failures,
|
||||
)
|
||||
require(
|
||||
bash_content,
|
||||
"git_head_changed=1",
|
||||
"bash integration no longer flags HEAD changes for immediate refresh",
|
||||
failures,
|
||||
)
|
||||
require(
|
||||
bash_content,
|
||||
'|| "$git_head_changed" == "1"',
|
||||
"bash integration no longer restarts running git probes on HEAD change",
|
||||
failures,
|
||||
)
|
||||
|
||||
if failures:
|
||||
print("FAIL: issue #666 regression(s) detected")
|
||||
for failure in failures:
|
||||
print(f"- {failure}")
|
||||
return 1
|
||||
|
||||
print("PASS: issue #666 checkout refresh guards are present")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Loading…
Add table
Add a link
Reference in a new issue