copyWorktreeIncludeFiles method
Copy gitignored files specified in .worktreeinclude from base repo to worktree.
Only copies files that are BOTH:
- Matched by patterns in .worktreeinclude (uses .gitignore syntax)
- Gitignored (not tracked by git)
Implementation
Future<List<String>> copyWorktreeIncludeFiles(
String repoRoot,
String worktreePath,
) async {
String includeContent;
try {
includeContent = await File('$repoRoot/.worktreeinclude').readAsString();
} catch (_) {
return [];
}
final patterns = includeContent
.split(RegExp(r'\r?\n'))
.map((line) => line.trim())
.where((line) => line.isNotEmpty && !line.startsWith('#'))
.toList();
if (patterns.isEmpty) return [];
// List gitignored files with --directory for performance
final gitignored = await _execGit([
'ls-files',
'--others',
'--ignored',
'--exclude-standard',
'--directory',
], cwd: repoRoot);
if (gitignored.code != 0 || gitignored.stdout.trim().isEmpty) return [];
final entries = gitignored.stdout
.trim()
.split('\n')
.where((e) => e.isNotEmpty)
.toList();
final files = <String>[];
final collapsedDirs = entries.where((e) => e.endsWith('/')).toList();
// Simple pattern matching for gitignore-style patterns
for (final entry in entries) {
if (entry.endsWith('/')) continue;
if (_matchesAnyPattern(entry, patterns)) {
files.add(entry);
}
}
// Expand collapsed directories if patterns target paths inside them
final dirsToExpand = collapsedDirs.where((dir) {
return patterns.any((p) {
final normalized = p.startsWith('/') ? p.substring(1) : p;
if (normalized.startsWith(dir)) return true;
final globIdx = normalized.indexOf(RegExp(r'[*?[]'));
if (globIdx > 0) {
final literalPrefix = normalized.substring(0, globIdx);
if (dir.startsWith(literalPrefix)) return true;
}
return false;
});
}).toList();
if (dirsToExpand.isNotEmpty) {
final expanded = await _execGit([
'ls-files',
'--others',
'--ignored',
'--exclude-standard',
'--',
...dirsToExpand,
], cwd: repoRoot);
if (expanded.code == 0 && expanded.stdout.trim().isNotEmpty) {
for (final f
in expanded.stdout.trim().split('\n').where((e) => e.isNotEmpty)) {
if (_matchesAnyPattern(f, patterns)) {
files.add(f);
}
}
}
}
final copied = <String>[];
for (final relativePath in files) {
final srcPath = '$repoRoot/$relativePath';
final destPath = '$worktreePath/$relativePath';
try {
final destDir = destPath.substring(0, destPath.lastIndexOf('/'));
await Directory(destDir).create(recursive: true);
await File(srcPath).copy(destPath);
copied.add(relativePath);
} catch (e) {
_logForDebugging(
'Failed to copy $relativePath to worktree: $e',
level: 'warn',
);
}
}
if (copied.isNotEmpty) {
_logForDebugging(
'Copied ${copied.length} files from .worktreeinclude: ${copied.join(', ')}',
);
}
return copied;
}