| 93 | * Node.js implementation of the Git interface using git CLI commands |
| 94 | */ |
| 95 | export class NodeGit implements Git { |
| 96 | private gitRepoCache = new Map<string, boolean>(); |
| 97 | private gitIgnoreCache = new Map< |
| 98 | string, |
| 99 | { filter: GitIgnoreFilter; mtime: number } |
| 100 | >(); |
| 101 | |
| 102 | isGitRepository(dir: string): boolean { |
| 103 | const normalizedDir = path.resolve(dir); |
| 104 | |
| 105 | const cached = this.gitRepoCache.get(normalizedDir); |
| 106 | if (cached !== undefined) { |
| 107 | return cached; |
| 108 | } |
| 109 | |
| 110 | let isGit = false; |
| 111 | try { |
| 112 | const result = spawnSync("git", ["rev-parse", "--git-dir"], { |
| 113 | cwd: dir, |
| 114 | encoding: "utf-8", |
| 115 | }); |
| 116 | isGit = result.status === 0 && !result.error; |
| 117 | } catch { |
| 118 | isGit = false; |
| 119 | } |
| 120 | |
| 121 | this.gitRepoCache.set(normalizedDir, isGit); |
| 122 | return isGit; |
| 123 | } |
| 124 | |
| 125 | /** |
| 126 | * Gets gitignore content from a git repository |
| 127 | */ |
| 128 | getGitIgnoreContent(repoRoot: string): string | null { |
| 129 | try { |
| 130 | const gitignorePath = path.join(repoRoot, ".gitignore"); |
| 131 | if (fs.existsSync(gitignorePath)) { |
| 132 | return fs.readFileSync(gitignorePath, "utf-8"); |
| 133 | } |
| 134 | } catch (error) { |
| 135 | // Log error but don't fail - .gitignore is optional |
| 136 | console.error( |
| 137 | `Warning: Failed to read .gitignore in ${repoRoot}:`, |
| 138 | error, |
| 139 | ); |
| 140 | } |
| 141 | return null; |
| 142 | } |
| 143 | |
| 144 | /** |
| 145 | * Gets files using git ls-files when in a git repository |
| 146 | */ |
| 147 | *getGitFiles(dirRoot: string): Generator<string> { |
| 148 | try { |
| 149 | const run = (args: string[]) => { |
| 150 | const res = spawnSync("git", args, { |
| 151 | cwd: dirRoot, |
| 152 | encoding: "utf-8", |
nothing calls this directly
no outgoing calls
no test coverage detected