Skip to content

copy_directories: excludeDirs post-hoc delete wastes time cloning large excluded subtrees #175

Description

@echarrod

Summary

copy_directories in lib/copy.sh copies the full parent directory first (via _fast_copy_dir), then calls _apply_directory_excludes to rm -rf the excluded subdirectories afterwards. For large excluded subtrees this means paying the full clone + delete cost every time a worktree is created, even though the data is immediately thrown away.

Reproduction

Add a large directory to includeDirs and exclude a large subdirectory of it via excludeDirs:

[copy]
    includeDirs = .claude
    excludeDirs = .claude/worktrees

Where .claude/worktrees contains ~150,000 files / 3.1 GB (Claude Code session state).

Running git gtr new <branch> takes ~70 extra seconds per worktree:

  • ~48s for cp -cRP .claude <worktree>/ (APFS clonefile still allocates per-inode metadata for 150k files)
  • ~22s for rm -rf <worktree>/.claude/worktrees

Root cause

In copy_directories (line 369), is_excluded is checked against the top-level dir_path (e.g. .claude). The exclude patterns (e.g. .claude/worktrees) don't match .claude, so the full directory is cloned. _apply_directory_excludes is only called at line 381 after _fast_copy_dir has already completed.

# copy.sh, inside copy_directories:
is_excluded "$dir_path" "$excludes" && continue   # .claude doesn't match .claude/worktrees → proceeds

_fast_copy_dir "$dir_path" "$dest_parent/"        # clones ALL 3.1 GB including worktrees
_apply_directory_excludes "$dest_parent" "$dir_path" "$excludes"  # rm -rf worktrees after the fact

Expected behaviour

When excludeDirs specifies a direct child of an includeDirs directory, the excluded subtree should be skipped during the copy rather than deleted afterwards. On macOS this could be achieved with rsync --exclude or by walking one level and selectively cloning; on Linux cp --reflink=auto combined with pre-filtering would work similarly.

Workaround

Replace broad includeDirs = .parent entries with specific includeDirs and include entries for only the subdirectories/files you actually need, avoiding the large excluded subtree entirely.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions