Skip to content

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

@echarrod

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
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions