| 142 | } |
| 143 | |
| 144 | function rebaseGitignorePattern( |
| 145 | rawPattern: string, |
| 146 | relativeDirPath: string, |
| 147 | ): string { |
| 148 | // Preserve negation and directory-only flags |
| 149 | const isNegated = rawPattern.startsWith('!') |
| 150 | let pattern = isNegated ? rawPattern.slice(1) : rawPattern |
| 151 | |
| 152 | const dirOnly = pattern.endsWith('/') |
| 153 | // Strip the trailing slash for slash-detection only |
| 154 | const core = dirOnly ? pattern.slice(0, -1) : pattern |
| 155 | |
| 156 | const anchored = core.startsWith('/') // anchored to .gitignore dir |
| 157 | // Detect if the "meaningful" part (minus optional leading '/' and trailing '/') |
| 158 | // contains a slash. If not, git treats it as recursive. |
| 159 | const coreNoLead = anchored ? core.slice(1) : core |
| 160 | const hasSlash = coreNoLead.includes('/') |
| 161 | |
| 162 | // Build the base (where this .gitignore lives relative to projectRoot) |
| 163 | const base = relativeDirPath.replace(/\\/g, '/') // normalize |
| 164 | |
| 165 | let rebased: string |
| 166 | if (anchored) { |
| 167 | // "/foo" from evals/.gitignore -> "evals/foo" |
| 168 | rebased = base ? `${base}/${coreNoLead}` : coreNoLead |
| 169 | } else if (!hasSlash) { |
| 170 | // "logs" or "logs/" should recurse from evals/: "evals/**/logs[/]" |
| 171 | if (base) { |
| 172 | rebased = `${base}/**/${coreNoLead}` |
| 173 | } else { |
| 174 | // At project root already; "logs" stays "logs" to keep recursive semantics |
| 175 | rebased = coreNoLead |
| 176 | } |
| 177 | } else { |
| 178 | // "foo/bar" relative to evals/: "evals/foo/bar" |
| 179 | rebased = base ? `${base}/${coreNoLead}` : coreNoLead |
| 180 | } |
| 181 | |
| 182 | if (dirOnly && !rebased.endsWith('/')) { |
| 183 | rebased += '/' |
| 184 | } |
| 185 | |
| 186 | // Normalize to forward slashes |
| 187 | rebased = rebased.replace(/\\/g, '/') |
| 188 | |
| 189 | return isNegated ? `!${rebased}` : rebased |
| 190 | } |
| 191 | |
| 192 | export async function parseGitignore(params: { |
| 193 | fullDirPath: string |