feat(git-worktree): auto-trust mise and direnv configs in new worktrees (#312)
This commit is contained in:
@@ -16,6 +16,7 @@ This skill provides a unified interface for managing Git worktrees across your d
|
|||||||
- **Interactive confirmations** at each step
|
- **Interactive confirmations** at each step
|
||||||
- **Automatic .gitignore management** for worktree directory
|
- **Automatic .gitignore management** for worktree directory
|
||||||
- **Automatic .env file copying** from main repo to new worktrees
|
- **Automatic .env file copying** from main repo to new worktrees
|
||||||
|
- **Automatic dev tool trusting** for mise and direnv configs with review-safe guardrails
|
||||||
|
|
||||||
## CRITICAL: Always Use the Manager Script
|
## CRITICAL: Always Use the Manager Script
|
||||||
|
|
||||||
@@ -23,8 +24,11 @@ This skill provides a unified interface for managing Git worktrees across your d
|
|||||||
|
|
||||||
The script handles critical setup that raw git commands don't:
|
The script handles critical setup that raw git commands don't:
|
||||||
1. Copies `.env`, `.env.local`, `.env.test`, etc. from main repo
|
1. Copies `.env`, `.env.local`, `.env.test`, etc. from main repo
|
||||||
2. Ensures `.worktrees` is in `.gitignore`
|
2. Trusts dev tool configs with branch-aware safety rules:
|
||||||
3. Creates consistent directory structure
|
- mise: auto-trust only when unchanged from a trusted baseline branch
|
||||||
|
- direnv: auto-allow only for trusted base branches; review worktrees stay manual
|
||||||
|
3. Ensures `.worktrees` is in `.gitignore`
|
||||||
|
4. Creates consistent directory structure
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# ✅ CORRECT - Always use the script
|
# ✅ CORRECT - Always use the script
|
||||||
@@ -95,7 +99,11 @@ bash ${CLAUDE_PLUGIN_ROOT}/skills/git-worktree/scripts/worktree-manager.sh creat
|
|||||||
2. Updates the base branch from remote
|
2. Updates the base branch from remote
|
||||||
3. Creates new worktree and branch
|
3. Creates new worktree and branch
|
||||||
4. **Copies all .env files from main repo** (.env, .env.local, .env.test, etc.)
|
4. **Copies all .env files from main repo** (.env, .env.local, .env.test, etc.)
|
||||||
5. Shows path for cd-ing to the worktree
|
5. **Trusts dev tool configs** with branch-aware safety rules:
|
||||||
|
- trusted bases (`main`, `develop`, `dev`, `trunk`, `staging`, `release/*`) compare against themselves
|
||||||
|
- other branches compare against the default branch
|
||||||
|
- direnv auto-allow is skipped on non-trusted bases because `.envrc` can source unchecked files
|
||||||
|
6. Shows path for cd-ing to the worktree
|
||||||
|
|
||||||
### `list` or `ls`
|
### `list` or `ls`
|
||||||
|
|
||||||
|
|||||||
@@ -65,6 +65,137 @@ copy_env_files() {
|
|||||||
echo -e " ${GREEN}✓ Copied $copied environment file(s)${NC}"
|
echo -e " ${GREEN}✓ Copied $copied environment file(s)${NC}"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Resolve the repository default branch, falling back to main when origin/HEAD
|
||||||
|
# is unavailable (for example in single-branch clones).
|
||||||
|
get_default_branch() {
|
||||||
|
local head_ref
|
||||||
|
head_ref=$(git symbolic-ref refs/remotes/origin/HEAD 2>/dev/null || true)
|
||||||
|
|
||||||
|
if [[ -n "$head_ref" ]]; then
|
||||||
|
echo "${head_ref#refs/remotes/origin/}"
|
||||||
|
else
|
||||||
|
echo "main"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Auto-trust is only safe when the worktree is created from a long-lived branch
|
||||||
|
# the developer already controls. Review/PR branches should fall back to the
|
||||||
|
# default branch baseline and require manual direnv approval.
|
||||||
|
is_trusted_base_branch() {
|
||||||
|
local branch="$1"
|
||||||
|
local default_branch="$2"
|
||||||
|
|
||||||
|
[[ "$branch" == "$default_branch" ]] && return 0
|
||||||
|
|
||||||
|
case "$branch" in
|
||||||
|
develop|dev|trunk|staging|release/*)
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
return 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
# Trust development tool configs in a new worktree.
|
||||||
|
# Worktrees get a new filesystem path that tools like mise and direnv
|
||||||
|
# have never seen. Without trusting, these tools block with interactive
|
||||||
|
# prompts or refuse to load configs, which breaks hooks and scripts.
|
||||||
|
#
|
||||||
|
# Safety: auto-trusts only configs unchanged from a trusted baseline branch.
|
||||||
|
# Review/PR branches fall back to the default-branch baseline, and direnv
|
||||||
|
# auto-allow is limited to trusted base branches because .envrc can source
|
||||||
|
# additional files that direnv does not validate.
|
||||||
|
#
|
||||||
|
# TOCTOU between hash-check and trust is acceptable for local dev use.
|
||||||
|
trust_dev_tools() {
|
||||||
|
local worktree_path="$1"
|
||||||
|
local base_ref="$2"
|
||||||
|
local allow_direnv_auto="$3"
|
||||||
|
local trusted=0
|
||||||
|
local skipped_messages=()
|
||||||
|
local manual_commands=()
|
||||||
|
|
||||||
|
# mise: trust the specific config file if present and unchanged
|
||||||
|
if command -v mise &>/dev/null; then
|
||||||
|
for f in .mise.toml mise.toml .tool-versions; do
|
||||||
|
if [[ -f "$worktree_path/$f" ]]; then
|
||||||
|
if _config_unchanged "$f" "$base_ref" "$worktree_path"; then
|
||||||
|
if (cd "$worktree_path" && mise trust "$f" --quiet); then
|
||||||
|
trusted=$((trusted + 1))
|
||||||
|
else
|
||||||
|
echo -e " ${YELLOW}Warning: 'mise trust $f' failed -- run manually in $worktree_path${NC}"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
skipped_messages+=("mise trust $f (config differs from $base_ref)")
|
||||||
|
manual_commands+=("mise trust $f")
|
||||||
|
fi
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
# direnv: allow .envrc
|
||||||
|
if command -v direnv &>/dev/null; then
|
||||||
|
if [[ -f "$worktree_path/.envrc" ]]; then
|
||||||
|
if [[ "$allow_direnv_auto" != "true" ]]; then
|
||||||
|
skipped_messages+=("direnv allow (.envrc auto-allow is disabled for non-trusted base branches)")
|
||||||
|
manual_commands+=("direnv allow")
|
||||||
|
elif _config_unchanged ".envrc" "$base_ref" "$worktree_path"; then
|
||||||
|
if (cd "$worktree_path" && direnv allow); then
|
||||||
|
trusted=$((trusted + 1))
|
||||||
|
else
|
||||||
|
echo -e " ${YELLOW}Warning: 'direnv allow' failed -- run manually in $worktree_path${NC}"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
skipped_messages+=("direnv allow (.envrc differs from $base_ref)")
|
||||||
|
manual_commands+=("direnv allow")
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ $trusted -gt 0 ]]; then
|
||||||
|
echo -e " ${GREEN}✓ Trusted $trusted dev tool config(s)${NC}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ ${#skipped_messages[@]} -gt 0 ]]; then
|
||||||
|
echo -e " ${YELLOW}Skipped auto-trust for config(s) requiring manual review:${NC}"
|
||||||
|
for item in "${skipped_messages[@]}"; do
|
||||||
|
echo -e " - $item"
|
||||||
|
done
|
||||||
|
if [[ ${#manual_commands[@]} -gt 0 ]]; then
|
||||||
|
local joined
|
||||||
|
joined=$(printf ' && %s' "${manual_commands[@]}")
|
||||||
|
echo -e " ${BLUE}Review the diff, then run manually: cd $worktree_path${joined}${NC}"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check if a config file is unchanged from the base branch.
|
||||||
|
# Returns 0 (true) if the file is identical to the base branch version.
|
||||||
|
# Returns 1 (false) if the file was added or modified by this branch.
|
||||||
|
#
|
||||||
|
# Note: rev-parse returns the stored blob hash; hash-object on a path applies
|
||||||
|
# gitattributes filters. A mismatch causes a false negative (trust skipped),
|
||||||
|
# which is the safe direction.
|
||||||
|
_config_unchanged() {
|
||||||
|
local file="$1"
|
||||||
|
local base_ref="$2"
|
||||||
|
local worktree_path="$3"
|
||||||
|
|
||||||
|
# Reject symlinks -- trust only regular files with verifiable content
|
||||||
|
[[ -L "$worktree_path/$file" ]] && return 1
|
||||||
|
|
||||||
|
# Get the blob hash directly from git's object database via rev-parse
|
||||||
|
local base_hash
|
||||||
|
base_hash=$(git rev-parse "$base_ref:$file" 2>/dev/null) || return 1
|
||||||
|
|
||||||
|
local worktree_hash
|
||||||
|
worktree_hash=$(git hash-object "$worktree_path/$file") || return 1
|
||||||
|
|
||||||
|
[[ "$base_hash" == "$worktree_hash" ]]
|
||||||
|
}
|
||||||
|
|
||||||
# Create a new worktree
|
# Create a new worktree
|
||||||
create_worktree() {
|
create_worktree() {
|
||||||
local branch_name="$1"
|
local branch_name="$1"
|
||||||
@@ -107,6 +238,29 @@ create_worktree() {
|
|||||||
# Copy environment files
|
# Copy environment files
|
||||||
copy_env_files "$worktree_path"
|
copy_env_files "$worktree_path"
|
||||||
|
|
||||||
|
# Trust dev tool configs (mise, direnv) so hooks and scripts work immediately.
|
||||||
|
# Long-lived integration branches can use themselves as the trust baseline,
|
||||||
|
# while review/PR branches fall back to the default branch and require manual
|
||||||
|
# direnv approval.
|
||||||
|
local default_branch
|
||||||
|
default_branch=$(get_default_branch)
|
||||||
|
local trust_branch="$default_branch"
|
||||||
|
local allow_direnv_auto="false"
|
||||||
|
if is_trusted_base_branch "$from_branch" "$default_branch"; then
|
||||||
|
trust_branch="$from_branch"
|
||||||
|
allow_direnv_auto="true"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! git fetch origin "$trust_branch" --quiet; then
|
||||||
|
echo -e " ${YELLOW}Warning: could not fetch origin/$trust_branch -- trust check may use stale data${NC}"
|
||||||
|
fi
|
||||||
|
# Skip trust entirely if the baseline ref doesn't exist locally.
|
||||||
|
if git rev-parse --verify "origin/$trust_branch" &>/dev/null; then
|
||||||
|
trust_dev_tools "$worktree_path" "origin/$trust_branch" "$allow_direnv_auto"
|
||||||
|
else
|
||||||
|
echo -e " ${YELLOW}Skipping dev tool trust -- origin/$trust_branch not found locally${NC}"
|
||||||
|
fi
|
||||||
|
|
||||||
echo -e "${GREEN}✓ Worktree created successfully!${NC}"
|
echo -e "${GREEN}✓ Worktree created successfully!${NC}"
|
||||||
echo ""
|
echo ""
|
||||||
echo "To switch to this worktree:"
|
echo "To switch to this worktree:"
|
||||||
@@ -321,6 +475,15 @@ Environment Files:
|
|||||||
- Creates .backup files if destination already exists
|
- Creates .backup files if destination already exists
|
||||||
- Use 'copy-env' to refresh env files after main repo changes
|
- Use 'copy-env' to refresh env files after main repo changes
|
||||||
|
|
||||||
|
Dev Tool Trust:
|
||||||
|
- Trusts mise config (.mise.toml, mise.toml, .tool-versions) and direnv (.envrc)
|
||||||
|
- Uses trusted base branches directly (main, develop, dev, trunk, staging, release/*)
|
||||||
|
- Other branches fall back to the default branch as the trust baseline
|
||||||
|
- direnv auto-allow is skipped on non-trusted base branches; review manually first
|
||||||
|
- Modified configs are flagged for manual review
|
||||||
|
- Only runs if the tool is installed and config exists
|
||||||
|
- Prevents hooks/scripts from hanging on interactive trust prompts
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
worktree-manager.sh create feature-login
|
worktree-manager.sh create feature-login
|
||||||
worktree-manager.sh create feature-auth develop
|
worktree-manager.sh create feature-auth develop
|
||||||
|
|||||||
Reference in New Issue
Block a user