85 lines
3.3 KiB
TypeScript
85 lines
3.3 KiB
TypeScript
import type { FolderReader } from './workspaceScope'
|
|
import { describe, expect, it } from 'vitest'
|
|
import { folderScope } from './workspaceScope'
|
|
|
|
/** 内存里的工作区文件夹:路径 → 内容(目录不用列,列出来的文件所在的目录都算有);记下读过哪些。 */
|
|
function memoryFolder(files: Record<string, string>): FolderReader & { asked: string[] } {
|
|
const asked: string[] = []
|
|
const directories = new Set(Object.keys(files).flatMap((file) => {
|
|
const parts = file.split('/')
|
|
return parts.slice(0, -1).map((_, i) => parts.slice(0, i + 1).join('/'))
|
|
}))
|
|
return {
|
|
asked,
|
|
exists: async (path) => {
|
|
asked.push(path)
|
|
return path in files || directories.has(path)
|
|
},
|
|
read: async (path) => {
|
|
asked.push(path)
|
|
return files[path]
|
|
},
|
|
}
|
|
}
|
|
|
|
async function kept(files: Record<string, string>, paths: string[], useIgnoreFiles = true): Promise<string[]> {
|
|
const scope = folderScope(memoryFolder(files), useIgnoreFiles)
|
|
const skipped = await Promise.all(paths.map(path => scope.skips(path)))
|
|
return paths.filter((_, i) => !skipped[i])
|
|
}
|
|
|
|
const TASKS = 'backend/src/luffy_agent/commerce/tasks.py'
|
|
const WORKTREE = '.claude/worktrees/agent-aa35211c29ea9e068'
|
|
|
|
describe('工作区扫描还要跳过的文件', () => {
|
|
it('嵌套的 git worktree(子目录里的 .git 是个文件)整个跳过,工作区自己的 .git 不算', async () => {
|
|
const files = {
|
|
'.git/HEAD': 'ref: refs/heads/main',
|
|
[TASKS]: '',
|
|
[`${WORKTREE}/.git`]: 'gitdir: /repo/.git/worktrees/agent-aa35211c29ea9e068',
|
|
[`${WORKTREE}/${TASKS}`]: '',
|
|
}
|
|
expect(await kept(files, [TASKS, `${WORKTREE}/${TASKS}`], false)).toEqual([TASKS])
|
|
})
|
|
|
|
it('嵌套的仓库(子目录里的 .git 是个目录)也跳过', async () => {
|
|
expect(await kept({ 'vendor/lib/.git/HEAD': '', 'vendor/lib/a.py': '', 'vendor/b.py': '' }, ['vendor/lib/a.py', 'vendor/b.py'])).toEqual(['vendor/b.py'])
|
|
})
|
|
|
|
it('.gitignore 忽略的目录跳过;每层的规则相对那一层,取反照 gitignore 写法;.ignore 也读', async () => {
|
|
const files = {
|
|
'.gitignore': '.claude/worktrees/\nbuild/\n*.log\n!keep.log\n',
|
|
'pkg/.gitignore': 'generated/\n',
|
|
'tools/.ignore': 'scratch.py\n',
|
|
[`${WORKTREE}/${TASKS}`]: '',
|
|
}
|
|
const paths = [
|
|
TASKS,
|
|
`${WORKTREE}/${TASKS}`,
|
|
'build/out/a.py',
|
|
'src/build.py',
|
|
'pkg/generated/a.py',
|
|
'generated/a.py',
|
|
'logs/x.log',
|
|
'logs/keep.log',
|
|
'tools/scratch.py',
|
|
'tools/run.py',
|
|
]
|
|
expect(await kept(files, paths)).toEqual([TASKS, 'src/build.py', 'generated/a.py', 'logs/keep.log', 'tools/run.py'])
|
|
})
|
|
|
|
it('search.useIgnoreFiles 关着时不读忽略文件,嵌套的 checkout 照样跳过', async () => {
|
|
const files = { '.gitignore': 'build/\n', 'sub/.git': 'gitdir: x', 'sub/a.py': '' }
|
|
expect(await kept(files, ['build/a.py', 'sub/a.py'], false)).toEqual(['build/a.py'])
|
|
})
|
|
|
|
it('同一个目录只查一次', async () => {
|
|
const folder = memoryFolder({ 'a/b/c.py': '', 'a/b/d.py': '' })
|
|
const scope = folderScope(folder, true)
|
|
await scope.skips('a/b/c.py')
|
|
await scope.skips('a/b/d.py')
|
|
expect(folder.asked.filter(path => path === 'a/b/.git')).toEqual(['a/b/.git'])
|
|
expect(folder.asked.filter(path => path === 'a/.gitignore')).toEqual(['a/.gitignore'])
|
|
})
|
|
})
|