Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 33 additions & 3 deletions src/core/file/fileSearch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,21 @@ const findEmptyDirectories = async (
return emptyDirs;
};

// Check if a path is a git worktree reference file
const isGitWorktreeRef = async (gitPath: string): Promise<boolean> => {
try {
const stats = await fs.stat(gitPath);
if (!stats.isFile()) {
return false;
}

const content = await fs.readFile(gitPath, 'utf8');
return content.startsWith('gitdir:');
} catch {
return false;
}
};

// Get all file paths considering the config
export const searchFiles = async (rootDir: string, config: RepomixConfigMerged): Promise<FileSearchResult> => {
// First check directory permissions
Expand All @@ -66,9 +81,24 @@ export const searchFiles = async (rootDir: string, config: RepomixConfigMerged):
logger.trace('Ignore patterns:', ignorePatterns);
logger.trace('Ignore file patterns:', ignoreFilePatterns);

// Check if .git is a worktree reference
const gitPath = path.join(rootDir, '.git');
const isWorktree = await isGitWorktreeRef(gitPath);

// Modify ignore patterns for git worktree
const adjustedIgnorePatterns = [...ignorePatterns];
if (isWorktree) {
// Remove '.git/**' pattern and add '.git' to ignore the reference file
const gitIndex = adjustedIgnorePatterns.indexOf('.git/**');
if (gitIndex !== -1) {
adjustedIgnorePatterns.splice(gitIndex, 1);
adjustedIgnorePatterns.push('.git');
}
}

const filePaths = await globby(includePatterns, {
cwd: rootDir,
ignore: [...ignorePatterns],
ignore: [...adjustedIgnorePatterns],
ignoreFiles: [...ignoreFilePatterns],
onlyFiles: true,
absolute: false,
Expand All @@ -89,15 +119,15 @@ export const searchFiles = async (rootDir: string, config: RepomixConfigMerged):
if (config.output.includeEmptyDirectories) {
const directories = await globby(includePatterns, {
cwd: rootDir,
ignore: [...ignorePatterns],
ignore: [...adjustedIgnorePatterns],
ignoreFiles: [...ignoreFilePatterns],
onlyDirectories: true,
absolute: false,
dot: true,
followSymbolicLinks: false,
});

emptyDirPaths = await findEmptyDirectories(rootDir, directories, ignorePatterns);
emptyDirPaths = await findEmptyDirectories(rootDir, directories, adjustedIgnorePatterns);
}

logger.trace(`Filtered ${filePaths.length} files`);
Expand Down
69 changes: 69 additions & 0 deletions tests/core/file/fileSearch.test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { Stats } from 'node:fs';
import * as fs from 'node:fs/promises';
import path from 'node:path';
import process from 'node:process';
Expand Down Expand Up @@ -272,5 +273,73 @@ node_modules
expect(result.filePaths).toContain('root/subdir/ignored.js');
expect(result.emptyDirPaths).toEqual([]);
});

test('should handle git worktree correctly', async () => {
// Mock .git file content for worktree
const gitWorktreeContent = 'gitdir: /path/to/main/repo/.git/worktrees/feature-branch';

// Mock fs.stat and fs.readFile for .git file
vi.mocked(fs.stat).mockResolvedValue({
isFile: () => true,
} as Stats);
vi.mocked(fs.readFile).mockResolvedValue(gitWorktreeContent);

// Mock globby to return some test files
vi.mocked(globby).mockResolvedValue(['file1.js', 'file2.js']);

const mockConfig = createMockConfig({
ignore: {
useGitignore: true,
useDefaultPatterns: true,
customPatterns: [],
},
});

const result = await searchFiles('/test/dir', mockConfig);

// Check that globby was called with correct ignore patterns
const globbyCall = vi.mocked(globby).mock.calls[0];
const ignorePatterns = globbyCall[1]?.ignore as string[];

// Verify .git file (not directory) is in ignore patterns
expect(ignorePatterns).toContain('.git');
// Verify .git/** is not in ignore patterns
expect(ignorePatterns).not.toContain('.git/**');

// Verify the files were returned correctly
expect(result.filePaths).toEqual(['file1.js', 'file2.js']);
});

test('should handle regular git repository correctly', async () => {
// Mock .git as a directory
vi.mocked(fs.stat).mockResolvedValue({
isFile: () => false,
} as Stats);

// Mock globby to return some test files
vi.mocked(globby).mockResolvedValue(['file1.js', 'file2.js']);

const mockConfig = createMockConfig({
ignore: {
useGitignore: true,
useDefaultPatterns: true,
customPatterns: [],
},
});

const result = await searchFiles('/test/dir', mockConfig);

// Check that globby was called with correct ignore patterns
const globbyCall = vi.mocked(globby).mock.calls[0];
const ignorePatterns = globbyCall[1]?.ignore as string[];

// Verify .git/** is in ignore patterns for regular git repos
expect(ignorePatterns).toContain('.git/**');
// Verify just .git is not in ignore patterns
expect(ignorePatterns).not.toContain('.git');

// Verify the files were returned correctly
expect(result.filePaths).toEqual(['file1.js', 'file2.js']);
});
});
});