source dump of claude code
at main 99 lines 3.2 kB view raw
1import { appendFile, mkdir, readFile, writeFile } from 'fs/promises' 2import { homedir } from 'os' 3import { dirname, join } from 'path' 4import { getCwd } from '../cwd.js' 5import { getErrnoCode } from '../errors.js' 6import { execFileNoThrowWithCwd } from '../execFileNoThrow.js' 7import { dirIsInGitRepo } from '../git.js' 8import { logError } from '../log.js' 9 10/** 11 * Checks if a path is ignored by git (via `git check-ignore`). 12 * 13 * This consults all applicable gitignore sources: repo `.gitignore` files 14 * (nested), `.git/info/exclude`, and the global gitignore — with correct 15 * precedence, because git itself resolves it. 16 * 17 * Exit codes: 0 = ignored, 1 = not ignored, 128 = not in a git repo. 18 * Returns `false` for 128, so callers outside a git repo fail open. 19 * 20 * @param filePath The path to check (absolute or relative to cwd) 21 * @param cwd The working directory to run git from 22 */ 23export async function isPathGitignored( 24 filePath: string, 25 cwd: string, 26): Promise<boolean> { 27 const { code } = await execFileNoThrowWithCwd( 28 'git', 29 ['check-ignore', filePath], 30 { 31 preserveOutputOnError: false, 32 cwd, 33 }, 34 ) 35 36 return code === 0 37} 38 39/** 40 * Gets the path to the global gitignore file (.config/git/ignore) 41 * @returns The path to the global gitignore file 42 */ 43export function getGlobalGitignorePath(): string { 44 return join(homedir(), '.config', 'git', 'ignore') 45} 46 47/** 48 * Adds a file pattern to the global gitignore file (.config/git/ignore) 49 * if it's not already ignored by existing patterns in any gitignore file 50 * @param filename The filename to add to gitignore 51 * @param cwd The current working directory (optional) 52 */ 53export async function addFileGlobRuleToGitignore( 54 filename: string, 55 cwd: string = getCwd(), 56): Promise<void> { 57 try { 58 if (!(await dirIsInGitRepo(cwd))) { 59 return 60 } 61 62 // First check if the pattern is already ignored by any gitignore file (including global) 63 const gitignoreEntry = `**/${filename}` 64 // For directory patterns (ending with /), check with a sample file inside 65 const testPath = filename.endsWith('/') 66 ? `${filename}sample-file.txt` 67 : filename 68 if (await isPathGitignored(testPath, cwd)) { 69 // File is already ignored by existing patterns (local or global) 70 return 71 } 72 73 // Use the global gitignore file in .config/git/ignore 74 const globalGitignorePath = getGlobalGitignorePath() 75 76 // Create the directory if it doesn't exist 77 const configGitDir = dirname(globalGitignorePath) 78 await mkdir(configGitDir, { recursive: true }) 79 80 // Add the entry to the global gitignore 81 try { 82 const content = await readFile(globalGitignorePath, { encoding: 'utf-8' }) 83 if (content.includes(gitignoreEntry)) { 84 return // Pattern already exists, don't add again 85 } 86 await appendFile(globalGitignorePath, `\n${gitignoreEntry}\n`) 87 } catch (e: unknown) { 88 const code = getErrnoCode(e) 89 if (code === 'ENOENT') { 90 // Create global gitignore with entry 91 await writeFile(globalGitignorePath, `${gitignoreEntry}\n`, 'utf-8') 92 } else { 93 throw e 94 } 95 } 96 } catch (error) { 97 logError(error) 98 } 99}