(opts: CleanWithDeps)
| 230 | * untracked path is a candidate. |
| 231 | */ |
| 232 | export async function cleanWith(opts: CleanWithDeps): Promise<string[]> { |
| 233 | const dir = opts.dir ?? "/"; |
| 234 | let matrix: StatusMatrixRow[]; |
| 235 | try { |
| 236 | matrix = await opts.git.statusMatrix({ fs: opts.fs, dir, cache: opts.cache }); |
| 237 | } catch (cause) { |
| 238 | if (isNotARepositoryCause(cause)) throw new NotARepositoryError(dir, { cause }); |
| 239 | throw new GitError("ECLEANFAIL", `git clean failed: ${errorMessage(cause)}`, { cause }); |
| 240 | } |
| 241 | |
| 242 | // Tracked directories: every ancestor of a path present in HEAD |
| 243 | // or the index. The repo root (".") is tracked whenever any |
| 244 | // file is. Untracked files whose parent is tracked are loose |
| 245 | // files; those nested under an untracked directory are grouped |
| 246 | // under that directory. |
| 247 | const trackedDirs = new Set<string>(["."]); |
| 248 | const untrackedFiles: string[] = []; |
| 249 | for (const [path, head, _workdir, stage] of matrix) { |
| 250 | if (head === 1 || stage !== 0) { |
| 251 | for (const ancestor of ancestorDirs(path)) trackedDirs.add(ancestor); |
| 252 | } else { |
| 253 | untrackedFiles.push(path); |
| 254 | } |
| 255 | } |
| 256 | |
| 257 | const looseFiles: string[] = []; |
| 258 | const untrackedTopDirs = new Set<string>(); |
| 259 | for (const path of untrackedFiles) { |
| 260 | const parent = dirname(path); |
| 261 | if (trackedDirs.has(parent)) { |
| 262 | looseFiles.push(path); |
| 263 | } else { |
| 264 | untrackedTopDirs.add(topmostUntrackedDir(path, trackedDirs)); |
| 265 | } |
| 266 | } |
| 267 | |
| 268 | const removed = [...looseFiles]; |
| 269 | if (opts.directories) removed.push(...untrackedTopDirs); |
| 270 | removed.sort(); |
| 271 | |
| 272 | if (opts.dryRun) return removed; |
| 273 | |
| 274 | const fs = opts.fs as RemoveFsClient; |
| 275 | for (const rel of removed) { |
| 276 | const abs = dir === "/" ? `/${rel}` : `${dir}/${rel}`; |
| 277 | await removePath(fs, abs); |
| 278 | } |
| 279 | return removed; |
| 280 | } |
| 281 | |
| 282 | async function removePath(fs: RemoveFsClient, abs: string): Promise<void> { |
| 283 | if (typeof fs.promises.rm === "function") { |
no test coverage detected