From d2354e62fb9b1e81541b52f9d84b3607d1117940 Mon Sep 17 00:00:00 2001 From: matheuscoelhomalta Date: Wed, 5 Feb 2025 18:43:30 -0300 Subject: [PATCH 1/4] fix: support paths with parentheses in include patterns - Add escapeGlobPattern function to handle special characters - Modify searchFiles to escape include patterns - Handle (), [], and {} characters in paths --- src/core/file/fileSearch.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/core/file/fileSearch.ts b/src/core/file/fileSearch.ts index 8e3e885e4..fffb1e98e 100644 --- a/src/core/file/fileSearch.ts +++ b/src/core/file/fileSearch.ts @@ -57,6 +57,14 @@ const isGitWorktreeRef = async (gitPath: string): Promise => { } }; +/** + * Escapes special characters in glob patterns to handle paths with parentheses. + * Example: "src/(categories)" -> "src/\\(categories\\)" + */ +const escapeGlobPattern = (pattern: string): string => { + return pattern.replace(/[()[\]{}]/g, '\\$&'); +}; + // Get all file paths considering the config export const searchFiles = async (rootDir: string, config: RepomixConfigMerged): Promise => { // First check directory permissions @@ -69,7 +77,9 @@ export const searchFiles = async (rootDir: string, config: RepomixConfigMerged): throw new Error(`Cannot access directory ${rootDir}: ${permissionCheck.error?.message}`); } - const includePatterns = config.include.length > 0 ? config.include : ['**/*']; + const includePatterns = config.include.length > 0 + ? config.include.map(pattern => escapeGlobPattern(pattern)) + : ['**/*']; try { const [ignorePatterns, ignoreFilePatterns] = await Promise.all([ From aefed0046d4d6a8a9d0711924a8f21648734fcf3 Mon Sep 17 00:00:00 2001 From: matheuscoelhomalta Date: Wed, 5 Feb 2025 18:53:37 -0300 Subject: [PATCH 2/4] lint fix --- src/core/file/fileSearch.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/core/file/fileSearch.ts b/src/core/file/fileSearch.ts index fffb1e98e..f385093fb 100644 --- a/src/core/file/fileSearch.ts +++ b/src/core/file/fileSearch.ts @@ -77,9 +77,8 @@ export const searchFiles = async (rootDir: string, config: RepomixConfigMerged): throw new Error(`Cannot access directory ${rootDir}: ${permissionCheck.error?.message}`); } - const includePatterns = config.include.length > 0 - ? config.include.map(pattern => escapeGlobPattern(pattern)) - : ['**/*']; + const includePatterns = + config.include.length > 0 ? config.include.map((pattern) => escapeGlobPattern(pattern)) : ['**/*']; try { const [ignorePatterns, ignoreFilePatterns] = await Promise.all([ From b92fbace636826db2b8cc8a6836e3c6a7eeb4078 Mon Sep 17 00:00:00 2001 From: Yamada Dev Date: Sat, 8 Feb 2025 01:14:34 +0900 Subject: [PATCH 3/4] test: Add escapeGlobPattern test --- src/core/file/fileSearch.ts | 2 +- tests/core/file/fileSearch.test.ts | 32 ++++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/src/core/file/fileSearch.ts b/src/core/file/fileSearch.ts index f385093fb..cde39ad6f 100644 --- a/src/core/file/fileSearch.ts +++ b/src/core/file/fileSearch.ts @@ -61,7 +61,7 @@ const isGitWorktreeRef = async (gitPath: string): Promise => { * Escapes special characters in glob patterns to handle paths with parentheses. * Example: "src/(categories)" -> "src/\\(categories\\)" */ -const escapeGlobPattern = (pattern: string): string => { +export const escapeGlobPattern = (pattern: string): string => { return pattern.replace(/[()[\]{}]/g, '\\$&'); }; diff --git a/tests/core/file/fileSearch.test.ts b/tests/core/file/fileSearch.test.ts index ab89b7142..2c116b3b2 100644 --- a/tests/core/file/fileSearch.test.ts +++ b/tests/core/file/fileSearch.test.ts @@ -6,6 +6,7 @@ import { globby } from 'globby'; import { minimatch } from 'minimatch'; import { beforeEach, describe, expect, test, vi } from 'vitest'; import { + escapeGlobPattern, getIgnoreFilePatterns, getIgnorePatterns, parseIgnoreContent, @@ -342,4 +343,35 @@ node_modules expect(result.filePaths).toEqual(['file1.js', 'file2.js']); }); }); + + describe('escapeGlobPattern', () => { + test('should escape parentheses in pattern', () => { + const pattern = 'src/(categories)/**/*.ts'; + expect(escapeGlobPattern(pattern)).toBe('src/\\(categories\\)/**/*.ts'); + }); + + test('should escape multiple types of brackets', () => { + const pattern = 'src/(auth)/[id]/{slug}/**/*.ts'; + expect(escapeGlobPattern(pattern)).toBe('src/\\(auth\\)/\\[id\\]/\\{slug\\}/**/*.ts'); + }); + + test('should handle nested brackets', () => { + const pattern = 'src/(auth)/([id])/**/*.ts'; + expect(escapeGlobPattern(pattern)).toBe('src/\\(auth\\)/\\(\\[id\\]\\)/**/*.ts'); + }); + + test('should handle empty string', () => { + expect(escapeGlobPattern('')).toBe(''); + }); + + test('should not modify patterns without special characters', () => { + const pattern = 'src/components/**/*.ts'; + expect(escapeGlobPattern(pattern)).toBe(pattern); + }); + + test('should handle multiple occurrences of the same bracket type', () => { + const pattern = 'src/(auth)/(settings)/**/*.ts'; + expect(escapeGlobPattern(pattern)).toBe('src/\\(auth\\)/\\(settings\\)/**/*.ts'); + }); + }); }); From 86de7c1aec722917fa9b0d9dbe5341a6ce62f863 Mon Sep 17 00:00:00 2001 From: Yamada Dev Date: Sat, 8 Feb 2025 01:29:06 +0900 Subject: [PATCH 4/4] fix(file): Properly escape backslashes in glob patterns --- src/core/file/fileSearch.ts | 5 ++++- tests/core/file/fileSearch.test.ts | 15 +++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/core/file/fileSearch.ts b/src/core/file/fileSearch.ts index cde39ad6f..e632d8a4a 100644 --- a/src/core/file/fileSearch.ts +++ b/src/core/file/fileSearch.ts @@ -62,7 +62,10 @@ const isGitWorktreeRef = async (gitPath: string): Promise => { * Example: "src/(categories)" -> "src/\\(categories\\)" */ export const escapeGlobPattern = (pattern: string): string => { - return pattern.replace(/[()[\]{}]/g, '\\$&'); + // First escape backslashes + const escapedBackslashes = pattern.replace(/\\/g, '\\\\'); + // Then escape special characters + return escapedBackslashes.replace(/[()[\]{}]/g, '\\$&'); }; // Get all file paths considering the config diff --git a/tests/core/file/fileSearch.test.ts b/tests/core/file/fileSearch.test.ts index 2c116b3b2..35ff061b2 100644 --- a/tests/core/file/fileSearch.test.ts +++ b/tests/core/file/fileSearch.test.ts @@ -374,4 +374,19 @@ node_modules expect(escapeGlobPattern(pattern)).toBe('src/\\(auth\\)/\\(settings\\)/**/*.ts'); }); }); + + test('should escape backslashes in pattern', () => { + const pattern = 'src\\temp\\(categories)'; + expect(escapeGlobPattern(pattern)).toBe('src\\\\temp\\\\\\(categories\\)'); + }); + + test('should handle patterns with already escaped special characters', () => { + const pattern = 'src\\\\(categories)'; + expect(escapeGlobPattern(pattern)).toBe('src\\\\\\\\\\(categories\\)'); + }); + + test('should handle patterns with mixed backslashes and special characters', () => { + const pattern = 'src\\temp\\[id]\\{slug}'; + expect(escapeGlobPattern(pattern)).toBe('src\\\\temp\\\\\\[id\\]\\\\\\{slug\\}'); + }); });