From 53dc7a641ab21691ee6cf87786aff3e7185c9d88 Mon Sep 17 00:00:00 2001 From: Marian Rudzynski Date: Thu, 30 Oct 2025 17:12:23 +0000 Subject: [PATCH 1/9] refactor(shared-symlinks): selective .claude subdirectory symlinking and eliminate duplication --- ...change_after_setup-shared-symlinks.sh.tmpl | 151 ++++++++++-------- 1 file changed, 86 insertions(+), 65 deletions(-) diff --git a/.chezmoiscripts/run_onchange_after_setup-shared-symlinks.sh.tmpl b/.chezmoiscripts/run_onchange_after_setup-shared-symlinks.sh.tmpl index 23fd16a..f9cd3fe 100644 --- a/.chezmoiscripts/run_onchange_after_setup-shared-symlinks.sh.tmpl +++ b/.chezmoiscripts/run_onchange_after_setup-shared-symlinks.sh.tmpl @@ -3,83 +3,100 @@ set -e {{ if .include_defaults -}} -# Check if running inside a Coder workspace -if [ "$CODER" = "true" ]; then +# ======================================== +# Function: Handle .claude directory symlinking +# ======================================== +symlink_claude() { + local source_claude_dir="$1" + + # If ~/.claude doesn't exist OR is a symlink, symlink the entire directory + if [ ! -e "$HOME/.claude" ] || [ -L "$HOME/.claude" ]; then + ln -sf "$source_claude_dir" "$HOME/.claude" + else + # If ~/.claude is a real directory, only symlink specific subdirectories and files + for subdir in agents commands skills; do + if [ -d "$source_claude_dir/$subdir" ]; then + ln -sf "$source_claude_dir/$subdir" "$HOME/.claude/$subdir" + fi + done + # Also symlink CLAUDE.md if it exists + if [ -f "$source_claude_dir/CLAUDE.md" ]; then + ln -sf "$source_claude_dir/CLAUDE.md" "$HOME/.claude/CLAUDE.md" + fi + fi +} - # ======================================== - # Setup shared home directory structure - # ======================================== +# ======================================== +# Function: Symlink items to home directory +# ======================================== +# Used only in Coder workspaces to symlink from /shared/ directories +symlink_to_home() { + local source_dir="$1" + local force_overwrite="${2:-false}" # If true, overwrite existing symlinks - # Use DOTFILES_SHARED_HOME env var, default to /shared/home/default - SHARED_HOME="${DOTFILES_SHARED_HOME:-/shared/home/default}" + for item in "$source_dir"/.*; do + [ -e "$item" ] || continue + [ "$(basename "$item")" = "." ] && continue + [ "$(basename "$item")" = ".." ] && continue + + basename_item=$(basename "$item") - # Ensure shared home exists + # Skip .config (managed by chezmoi) + [ "$basename_item" = ".config" ] && continue + + # Handle .claude specially + if [ "$basename_item" = ".claude" ]; then + symlink_claude "$item" + continue + fi + + # For other items + target="$HOME/$basename_item" + + if [ "$force_overwrite" = "true" ]; then + # Force symlink (overrides existing) + ln -sf "$item" "$target" + else + # Only create symlink if target doesn't exist + if [ ! -e "$target" ]; then + ln -sf "$item" "$target" + fi + fi + done +} + +# ======================================== +# CODER WORKSPACE SETUP +# ======================================== +# Uses persistent /shared/ directories for profile-based configuration +# that survives workspace rebuilds + +if [ "$CODER" = "true" ]; then + SHARED_HOME="${DOTFILES_SHARED_HOME:-/shared/home/default}" mkdir -p "$SHARED_HOME" - # Seed missing files/directories from dotfiles/shared/ as defaults + # Seed /shared/home/{profile}/ from dotfiles if empty if [ -d "{{ .chezmoi.sourceDir }}/shared" ]; then for item in {{ .chezmoi.sourceDir }}/shared/*; do [ -e "$item" ] || continue - basename_item=$(basename "$item") - # Add dot prefix for home directory target="$SHARED_HOME/.$basename_item" - - # Only copy if it doesn't exist in shared home if [ ! -e "$target" ]; then cp -r "$item" "$target" fi done fi - # First, symlink from /shared/home/shared/ (universal for all profiles) + # Symlink from /shared/home/shared/ (universal for all profiles) if [ -d "/shared/home/shared" ]; then - for item in /shared/home/shared/.*; do - [ -e "$item" ] || continue - [ "$(basename "$item")" = "." ] && continue - [ "$(basename "$item")" = ".." ] && continue - - basename_item=$(basename "$item") - - # Skip .config (managed by chezmoi) - [ "$basename_item" = ".config" ] && continue - - # Target path in home directory - target="$HOME/$basename_item" - - # Create symlink only if target doesn't exist - if [ ! -e "$target" ]; then - ln -sf "$item" "$target" - fi - done + symlink_to_home "/shared/home/shared" false fi - # Then, symlink from profile-specific shared home (can override shared) - for item in "$SHARED_HOME"/.*; do - [ -e "$item" ] || continue - [ "$(basename "$item")" = "." ] && continue - [ "$(basename "$item")" = ".." ] && continue - - basename_item=$(basename "$item") + # Symlink from /shared/home/{profile}/ (profile-specific, can override) + symlink_to_home "$SHARED_HOME" true - # Skip .config (managed by chezmoi) - [ "$basename_item" = ".config" ] && continue - - # Target path in home directory - target="$HOME/$basename_item" - - # Force symlink (overrides shared if exists) - ln -sf "$item" "$target" - done - - # ======================================== - # Setup GitHub CLI shared authentication - # ======================================== - - # Define shared GitHub CLI config path for maintainability + # GitHub CLI: Symlink ~/.config/gh to /shared/.config/gh GH_SHARED_CONFIG="/shared/.config/gh" - - # Seed /shared/.config/gh from dotfiles/shared/gh if it doesn't exist if [ -d "{{ .chezmoi.sourceDir }}/shared/gh" ] && [ ! -d "$GH_SHARED_CONFIG" ]; then echo "Seeding $GH_SHARED_CONFIG from dotfiles..." mkdir -p /shared/.config @@ -88,38 +105,42 @@ if [ "$CODER" = "true" ]; then echo "✓ Created $GH_SHARED_CONFIG (update hosts.yml with your token)" fi - # Create symlink from ~/.config/gh to /shared/.config/gh if [ -d "$GH_SHARED_CONFIG" ]; then echo "Setting up GitHub CLI shared authentication..." - - # Backup and remove existing gh config if it exists and is not a symlink if [ -e "$HOME/.config/gh" ] && [ ! -L "$HOME/.config/gh" ]; then - echo "Backing up existing GitHub CLI config to $GH_SHARED_CONFIG.backup before removal..." timestamp=$(date +"%Y%m%d_%H%M%S") backup_dir="${GH_SHARED_CONFIG}.backup_$timestamp" cp -r "$HOME/.config/gh" "$backup_dir" echo "✓ Backed up to $backup_dir" rm -rf "$HOME/.config/gh" fi - - # Create the symlink (ensure parent directory exists) mkdir -p "$HOME/.config" ln -sfn "$GH_SHARED_CONFIG" "$HOME/.config/gh" echo "✓ GitHub CLI symlinked to $GH_SHARED_CONFIG" fi +# ======================================== +# NON-CODER ENVIRONMENT SETUP +# ======================================== +# Symlinks directly from dotfiles repo (no /shared/ directory exists) + else - # Outside of Coder, symlink from dotfiles shared/ directory if [ -d "{{ .chezmoi.sourceDir }}/shared" ]; then for item in {{ .chezmoi.sourceDir }}/shared/*; do [ -e "$item" ] || continue basename_item=$(basename "$item") - # Only symlink if doesn't exist in home (with dot prefix) + # Handle claude specially (no dot prefix in source) + if [ "$basename_item" = "claude" ]; then + symlink_claude "$item" + continue + fi + + # Symlink other items if they don't exist (add dot prefix) if [ ! -e "$HOME/.$basename_item" ]; then ln -sf "$item" "$HOME/.$basename_item" fi done fi fi -{{ end -}} \ No newline at end of file +{{ end -}} From e47856265112e4a82bd2444af947ce9dfc8767ba Mon Sep 17 00:00:00 2001 From: Marian Rudzynski Date: Thu, 30 Oct 2025 17:26:12 +0000 Subject: [PATCH 2/9] test: add Docker-based CI test suite and skills directory --- .github/workflows/test.yml | 20 ++++ CLAUDE.md | 28 ++++++ shared/claude/skills/README.md | 1 + test/test-dotfiles.sh | 168 +++++++++++++++++++++++++++++++++ 4 files changed, 217 insertions(+) create mode 100644 .github/workflows/test.yml create mode 100644 shared/claude/skills/README.md create mode 100755 test/test-dotfiles.sh diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..df8e034 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,20 @@ +name: Test Dotfiles + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + test: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Run dotfiles tests + run: | + chmod +x test/test-dotfiles.sh + ./test/test-dotfiles.sh diff --git a/CLAUDE.md b/CLAUDE.md index ecfd83f..68b7ab6 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -108,6 +108,34 @@ To manually sync Claude configuration: - Work-specific agents, commands, or workflows - Personal information, credentials, or internal infrastructure details +## Testing + +### Local Testing + +The dotfiles include automated tests that verify both Coder and non-Coder installation scenarios using Docker. + +**Prerequisites:** +- Docker installed and running +- If Docker is not running: `sudo service docker start` + +**Run tests:** +```bash +./test/test-dotfiles.sh +``` + +**What it tests:** +1. **Non-Coder Environment**: Verifies direct symlinking from dotfiles repo +2. **Coder Fresh Install**: Verifies `/shared/` seeding and full directory symlink +3. **Coder Existing ~/.claude**: Verifies selective subdirectory symlinking (agents, commands, skills, CLAUDE.md) + +### CI Testing + +Tests run automatically on: +- Push to `main` branch +- Pull requests to `main` branch + +The CI workflow (`.github/workflows/test.yml`) uses GitHub Actions and runs the same test script in an Ubuntu environment. + ## Ultimate Goal Host a shell script on GitHub Pages (e.g., fx.github.io/dotfiles/install.sh) that can be run via: diff --git a/shared/claude/skills/README.md b/shared/claude/skills/README.md new file mode 100644 index 0000000..cec135f --- /dev/null +++ b/shared/claude/skills/README.md @@ -0,0 +1 @@ +# Skills directory diff --git a/test/test-dotfiles.sh b/test/test-dotfiles.sh new file mode 100755 index 0000000..5788286 --- /dev/null +++ b/test/test-dotfiles.sh @@ -0,0 +1,168 @@ +#!/bin/bash +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[0;33m' +NC='\033[0m' # No Color + +print_test() { echo -e "${YELLOW}TEST:${NC} $1"; } +print_pass() { echo -e "${GREEN}✓${NC} $1"; } +print_fail() { echo -e "${RED}✗${NC} $1"; } + +# Check if Docker is running +if ! docker info >/dev/null 2>&1; then + echo "Docker is not running or you don't have permission to access it." + echo "Try one of:" + echo " - sudo service docker start" + echo " - sudo ./test/test-dotfiles.sh" + echo " - sudo usermod -aG docker \$USER && newgrp docker" + exit 1 +fi + +IMAGE="ghcr.io/fx/docker/devcontainer:latest" + +# Pull the image +echo "Pulling Docker image: $IMAGE" +docker pull "$IMAGE" + +# Test 1: Non-Coder Environment +print_test "Non-Coder Environment (direct symlink from dotfiles)" +docker run --rm \ + -v "$REPO_ROOT:/workspace" \ + -w /workspace \ + "$IMAGE" \ + bash -c ' + # Install dotfiles in non-Coder mode + export CODER="" + bash install.sh default 2>&1 | grep -v "git config" || true + + # Verify ~/.claude exists and is a symlink + if [ -L "$HOME/.claude" ]; then + echo "✓ ~/.claude is a symlink" + else + echo "✗ ~/.claude is not a symlink" + exit 1 + fi + + # Verify all content is accessible + if [ -d "$HOME/.claude/agents" ]; then + echo "✓ agents directory accessible" + else + echo "✗ agents not accessible" + exit 1 + fi + ' + +print_pass "Non-Coder test passed" + +# Test 2: Coder Environment (fresh install) +print_test "Coder Environment - Fresh Install" +docker run --rm \ + -v "$REPO_ROOT:/workspace" \ + -w /workspace \ + -e CODER=true \ + --tmpfs /shared:rw,exec \ + "$IMAGE" \ + bash -c ' + # Install dotfiles in Coder mode + bash install.sh default 2>&1 | grep -v "git config" || true + + # Verify /shared was seeded + if [ -d "/shared/home/default/.claude" ]; then + echo "✓ /shared/home/default/.claude seeded" + else + echo "✗ /shared not seeded" + exit 1 + fi + + # Verify ~/.claude is a symlink to /shared + if [ -L "$HOME/.claude" ]; then + echo "✓ ~/.claude is a symlink" + target=$(readlink "$HOME/.claude") + if [[ "$target" == *"/shared/"* ]]; then + echo "✓ ~/.claude points to /shared" + else + echo "✗ ~/.claude does not point to /shared" + exit 1 + fi + else + echo "✗ ~/.claude is not a symlink" + exit 1 + fi + ' + +print_pass "Coder fresh install test passed" + +# Test 3: Coder Environment (existing ~/.claude) +print_test "Coder Environment - Existing ~/.claude" +docker run --rm \ + -v "$REPO_ROOT:/workspace" \ + -w /workspace \ + -e CODER=true \ + --tmpfs /shared:rw,exec \ + "$IMAGE" \ + bash -c ' + # Create existing ~/.claude with user content + mkdir -p "$HOME/.claude" + echo "user_settings" > "$HOME/.claude/settings.json" + echo "user_custom" > "$HOME/.claude/custom.txt" + + # Install dotfiles in Coder mode + bash install.sh default 2>&1 | grep -v "git config" || true + + # Verify ~/.claude is NOT a symlink (real directory) + if [ ! -L "$HOME/.claude" ] && [ -d "$HOME/.claude" ]; then + echo "✓ ~/.claude is a real directory" + else + echo "✗ ~/.claude is not a real directory" + exit 1 + fi + + # Verify user files are preserved + if [ -f "$HOME/.claude/settings.json" ]; then + echo "✓ User settings.json preserved" + else + echo "✗ User settings.json missing" + exit 1 + fi + + # Verify subdirectories are symlinks + if [ -L "$HOME/.claude/agents" ]; then + echo "✓ agents is a symlink" + else + echo "✗ agents is not a symlink" + exit 1 + fi + + if [ -L "$HOME/.claude/commands" ]; then + echo "✓ commands is a symlink" + else + echo "✗ commands is not a symlink" + exit 1 + fi + + if [ -L "$HOME/.claude/skills" ]; then + echo "✓ skills is a symlink" + else + echo "✗ skills is not a symlink" + exit 1 + fi + + # Verify CLAUDE.md is a symlink + if [ -L "$HOME/.claude/CLAUDE.md" ]; then + echo "✓ CLAUDE.md is a symlink" + else + echo "✗ CLAUDE.md is not a symlink" + exit 1 + fi + ' + +print_pass "Coder existing ~/.claude test passed" + +echo "" +print_pass "All tests passed!" From 1f257d0763e5838e24892ae63f0163826c8ba2cb Mon Sep 17 00:00:00 2001 From: Marian Rudzynski Date: Thu, 30 Oct 2025 18:03:18 +0000 Subject: [PATCH 3/9] fix: use ln -n flag to prevent symlinks inside existing directories --- .../run_onchange_after_setup-shared-symlinks.sh.tmpl | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.chezmoiscripts/run_onchange_after_setup-shared-symlinks.sh.tmpl b/.chezmoiscripts/run_onchange_after_setup-shared-symlinks.sh.tmpl index f9cd3fe..bdc900f 100644 --- a/.chezmoiscripts/run_onchange_after_setup-shared-symlinks.sh.tmpl +++ b/.chezmoiscripts/run_onchange_after_setup-shared-symlinks.sh.tmpl @@ -10,18 +10,19 @@ symlink_claude() { local source_claude_dir="$1" # If ~/.claude doesn't exist OR is a symlink, symlink the entire directory + # Use -n flag to treat destination as normal file (prevents creating symlink inside directory) if [ ! -e "$HOME/.claude" ] || [ -L "$HOME/.claude" ]; then - ln -sf "$source_claude_dir" "$HOME/.claude" + ln -sfn "$source_claude_dir" "$HOME/.claude" else # If ~/.claude is a real directory, only symlink specific subdirectories and files for subdir in agents commands skills; do if [ -d "$source_claude_dir/$subdir" ]; then - ln -sf "$source_claude_dir/$subdir" "$HOME/.claude/$subdir" + ln -sfn "$source_claude_dir/$subdir" "$HOME/.claude/$subdir" fi done # Also symlink CLAUDE.md if it exists if [ -f "$source_claude_dir/CLAUDE.md" ]; then - ln -sf "$source_claude_dir/CLAUDE.md" "$HOME/.claude/CLAUDE.md" + ln -sfn "$source_claude_dir/CLAUDE.md" "$HOME/.claude/CLAUDE.md" fi fi } From 817eea955989960feb4c890d0600f3637ea3fc6e Mon Sep 17 00:00:00 2001 From: Marian Rudzynski Date: Thu, 30 Oct 2025 18:09:44 +0000 Subject: [PATCH 4/9] fix(test): use local repository for third test to verify current branch changes --- test/test-dotfiles.sh | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/test/test-dotfiles.sh b/test/test-dotfiles.sh index 5788286..7c2c0c5 100755 --- a/test/test-dotfiles.sh +++ b/test/test-dotfiles.sh @@ -112,8 +112,14 @@ docker run --rm \ echo "user_settings" > "$HOME/.claude/settings.json" echo "user_custom" > "$HOME/.claude/custom.txt" - # Install dotfiles in Coder mode - bash install.sh default 2>&1 | grep -v "git config" || true + # Install mise and chezmoi first + curl -sSL https://mise.jdx.dev/install.sh | sh >/dev/null 2>&1 + export PATH="$HOME/.local/bin:$PATH" + eval "$(mise activate bash)" + mise use -g chezmoi@latest >/dev/null 2>&1 + + # Use local repository as source (not GitHub) + mise exec -- chezmoi init --apply --promptString profile=default /workspace 2>&1 | grep -v "git config" || true # Verify ~/.claude is NOT a symlink (real directory) if [ ! -L "$HOME/.claude" ] && [ -d "$HOME/.claude" ]; then From 4a26cea59dba26e322aeb885b433ace5b52a072b Mon Sep 17 00:00:00 2001 From: Marian Rudzynski Date: Thu, 30 Oct 2025 18:27:30 +0000 Subject: [PATCH 5/9] fix(shared-symlinks): add nullglob for safe glob expansion and improve documentation --- ...change_after_setup-shared-symlinks.sh.tmpl | 3 + .github/copilot-instructions.md | 8 ++ test/README.md | 75 +++++++++++++++++++ 3 files changed, 86 insertions(+) create mode 100644 .github/copilot-instructions.md create mode 100644 test/README.md diff --git a/.chezmoiscripts/run_onchange_after_setup-shared-symlinks.sh.tmpl b/.chezmoiscripts/run_onchange_after_setup-shared-symlinks.sh.tmpl index bdc900f..32c039e 100644 --- a/.chezmoiscripts/run_onchange_after_setup-shared-symlinks.sh.tmpl +++ b/.chezmoiscripts/run_onchange_after_setup-shared-symlinks.sh.tmpl @@ -3,6 +3,9 @@ set -e {{ if .include_defaults -}} +# Enable nullglob to prevent glob patterns from being treated as literals when no files match +shopt -s nullglob + # ======================================== # Function: Handle .claude directory symlinking # ======================================== diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 0000000..ac2226a --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,8 @@ +# GitHub Copilot Instructions + +## Code Reviews + +### Test Suite Conventions + +- Do not suggest refactoring test scripts to use `install.sh` when the test intentionally bypasses it to test specific chezmoi behavior (e.g., selective symlinking with pre-existing directories) +- Tests may directly invoke chezmoi commands when they need to control the environment state before installation diff --git a/test/README.md b/test/README.md new file mode 100644 index 0000000..c3170b9 --- /dev/null +++ b/test/README.md @@ -0,0 +1,75 @@ +# Dotfiles Testing + +This directory contains automated tests for the dotfiles installation system. + +## Test Suite + +The `test-dotfiles.sh` script validates dotfiles installation in three scenarios: + +1. **Non-Coder Environment**: Verifies direct symlinking from dotfiles repository +2. **Coder Fresh Install**: Verifies `/shared/` directory seeding and full directory symlink +3. **Coder Existing ~/.claude**: Verifies selective subdirectory symlinking when `~/.claude` already exists + +## Requirements + +### Docker Image + +The tests use a custom Docker image: `ghcr.io/fx/docker/devcontainer:latest` + +This image is built from the [fx/docker](https://github.com/fx/docker) repository and provides a consistent test environment with: +- Base Linux distribution for testing +- Common development tools +- User environment similar to actual workspaces + +**Note**: The image is publicly available from GitHub Container Registry. If you need to build it locally or use a different image, modify the `IMAGE` variable in `test-dotfiles.sh`. + +### Docker + +Docker must be installed and running: + +```bash +# Check if Docker is running +docker info + +# Start Docker if needed +sudo service docker start +``` + +## Running Tests + +### Local Testing + +```bash +# From repository root +./test/test-dotfiles.sh + +# Or with sudo if needed +sudo ./test/test-dotfiles.sh +``` + +### CI Testing + +Tests run automatically via GitHub Actions on: +- Push to `main` branch +- Pull requests to `main` branch + +See `.github/workflows/test.yml` for CI configuration. + +## Test Scenarios Explained + +### Test 1: Non-Coder Environment + +Simulates installation on a personal machine where `/shared/` directory doesn't exist. Verifies that `~/.claude` becomes a symlink directly to the dotfiles repository. + +### Test 2: Coder Fresh Install + +Simulates first-time installation in a Coder workspace. Verifies: +- `/shared/home/default/.claude` is seeded from dotfiles +- `~/.claude` is a symlink to `/shared/home/default/.claude` + +### Test 3: Coder Existing ~/.claude + +Simulates installation when user already has a `~/.claude` directory with custom content. Verifies: +- `~/.claude` remains a real directory (not symlink) +- User files are preserved +- Only subdirectories (`agents`, `commands`, `skills`) and `CLAUDE.md` are symlinked From 2110ba2699cf1873695f7dc796869b1a1319127e Mon Sep 17 00:00:00 2001 From: Marian Rudzynski Date: Thu, 30 Oct 2025 18:33:24 +0000 Subject: [PATCH 6/9] fix(test): add safe.directory config for Docker mount ownership The third test case was failing because Git inside the Docker container refused to access /workspace/.git due to dubious ownership (mounted from host with different uid/gid). This adds `git config --global --add safe.directory /workspace` before running chezmoi to resolve the issue. --- test/test-dotfiles.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/test-dotfiles.sh b/test/test-dotfiles.sh index 7c2c0c5..03421fe 100755 --- a/test/test-dotfiles.sh +++ b/test/test-dotfiles.sh @@ -118,6 +118,9 @@ docker run --rm \ eval "$(mise activate bash)" mise use -g chezmoi@latest >/dev/null 2>&1 + # Mark workspace as safe directory for Git + git config --global --add safe.directory /workspace + # Use local repository as source (not GitHub) mise exec -- chezmoi init --apply --promptString profile=default /workspace 2>&1 | grep -v "git config" || true From 01165216a41ed6c1e6cdc046dfebb300805fd779 Mon Sep 17 00:00:00 2001 From: Marian Rudzynski Date: Thu, 30 Oct 2025 18:37:54 +0000 Subject: [PATCH 7/9] fix(test): mark all directories as safe for Git in test environment Use `safe.directory '*'` instead of specific path to ensure all Git operations work correctly in the Docker test environment, regardless of mount ownership. --- test/test-dotfiles.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/test-dotfiles.sh b/test/test-dotfiles.sh index 03421fe..c270fe4 100755 --- a/test/test-dotfiles.sh +++ b/test/test-dotfiles.sh @@ -118,8 +118,8 @@ docker run --rm \ eval "$(mise activate bash)" mise use -g chezmoi@latest >/dev/null 2>&1 - # Mark workspace as safe directory for Git - git config --global --add safe.directory /workspace + # Mark all directories as safe for Git in test environment + git config --global --add safe.directory '*' # Use local repository as source (not GitHub) mise exec -- chezmoi init --apply --promptString profile=default /workspace 2>&1 | grep -v "git config" || true From e8111826b478219e8b7e9b59a2ec1f391194e237 Mon Sep 17 00:00:00 2001 From: Marian Rudzynski Date: Thu, 30 Oct 2025 18:41:18 +0000 Subject: [PATCH 8/9] fix(test): configure safe.directory before installing tools Move git config safe.directory before mise/chezmoi installation to ensure the configuration is in place when chezmoi needs to access the local repository. --- test/test-dotfiles.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/test-dotfiles.sh b/test/test-dotfiles.sh index c270fe4..f68bef9 100755 --- a/test/test-dotfiles.sh +++ b/test/test-dotfiles.sh @@ -112,15 +112,15 @@ docker run --rm \ echo "user_settings" > "$HOME/.claude/settings.json" echo "user_custom" > "$HOME/.claude/custom.txt" - # Install mise and chezmoi first + # Mark workspace as safe directory for Git BEFORE installing tools + git config --global --add safe.directory /workspace + + # Install mise and chezmoi curl -sSL https://mise.jdx.dev/install.sh | sh >/dev/null 2>&1 export PATH="$HOME/.local/bin:$PATH" eval "$(mise activate bash)" mise use -g chezmoi@latest >/dev/null 2>&1 - # Mark all directories as safe for Git in test environment - git config --global --add safe.directory '*' - # Use local repository as source (not GitHub) mise exec -- chezmoi init --apply --promptString profile=default /workspace 2>&1 | grep -v "git config" || true From 27c58b5cfa32b6532a8275e4497a605b03148c70 Mon Sep 17 00:00:00 2001 From: Marian Rudzynski Date: Thu, 30 Oct 2025 18:45:02 +0000 Subject: [PATCH 9/9] fix(test): copy repo to temp location to avoid Docker mount ownership issues Instead of using the mounted /workspace directly, copy it to /tmp inside the container first. This eliminates Git ownership validation errors that occur with Docker volume mounts having different uid/gid. --- test/test-dotfiles.sh | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/test/test-dotfiles.sh b/test/test-dotfiles.sh index f68bef9..12f5ca4 100755 --- a/test/test-dotfiles.sh +++ b/test/test-dotfiles.sh @@ -112,17 +112,22 @@ docker run --rm \ echo "user_settings" > "$HOME/.claude/settings.json" echo "user_custom" > "$HOME/.claude/custom.txt" - # Mark workspace as safe directory for Git BEFORE installing tools - git config --global --add safe.directory /workspace - # Install mise and chezmoi curl -sSL https://mise.jdx.dev/install.sh | sh >/dev/null 2>&1 export PATH="$HOME/.local/bin:$PATH" eval "$(mise activate bash)" mise use -g chezmoi@latest >/dev/null 2>&1 - # Use local repository as source (not GitHub) - mise exec -- chezmoi init --apply --promptString profile=default /workspace 2>&1 | grep -v "git config" || true + # Copy current branch content to a temporary location that chezmoi can access + # This avoids Git ownership issues with Docker mounts + TEMP_REPO="/tmp/test-dotfiles" + cp -r /workspace "$TEMP_REPO" + cd "$TEMP_REPO" + git config --global --add safe.directory "$TEMP_REPO" + + # Use the temporary repository copy + cd "$HOME" + mise exec -- chezmoi init --apply --promptString profile=default "$TEMP_REPO" 2>&1 | grep -v "git config" || true # Verify ~/.claude is NOT a symlink (real directory) if [ ! -L "$HOME/.claude" ] && [ -d "$HOME/.claude" ]; then