fix(core): stop repository discovery at nearest marker (#39714)
This commit is contained in:
parent
ce7a7e4e23
commit
9d55b223bb
4 changed files with 48 additions and 21 deletions
|
|
@ -201,7 +201,7 @@ const layer = Layer.effect(
|
|||
locks.withLock(repository.gitDirectory)(effect)
|
||||
|
||||
const discover = Effect.fn("Git.repo.discover")(function* (input: AbsolutePath) {
|
||||
const dotgit = yield* fs.up({ targets: [".git"], start: input }).pipe(
|
||||
const dotgit = yield* fs.up({ targets: [".git"], start: input, mode: "first" }).pipe(
|
||||
Effect.map((matches) => matches[0]),
|
||||
Effect.catch(() => Effect.succeed(undefined)),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ export const root = Effect.fn("Project.root")(function* (
|
|||
fs: FSUtil.Interface,
|
||||
input: AbsolutePath,
|
||||
) {
|
||||
return yield* fs.up({ targets: [".git", ".hg"], start: input }).pipe(
|
||||
return yield* fs.up({ targets: [".git", ".hg"], start: input, mode: "first" }).pipe(
|
||||
Effect.map((matches) => matches[0] ? AbsolutePath.make(path.dirname(matches[0])) : undefined),
|
||||
Effect.catch(() => Effect.succeed(undefined)),
|
||||
)
|
||||
|
|
@ -224,7 +224,7 @@ const layer = Layer.effect(
|
|||
})
|
||||
|
||||
const hgDiscover = Effect.fnUntraced(function* (input: AbsolutePath) {
|
||||
const dotHg = yield* fs.up({ targets: [".hg"], start: input }).pipe(
|
||||
const dotHg = yield* fs.up({ targets: [".hg"], start: input, mode: "first" }).pipe(
|
||||
Effect.map((matches) => matches[0]),
|
||||
Effect.catch(() => Effect.succeed(undefined)),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { describe, test, expect } from "bun:test"
|
||||
import { Effect, FileSystem } from "effect"
|
||||
import { Effect, FileSystem, Layer } from "effect"
|
||||
import { LayerNodePlatform } from "@opencode-ai/util/effect/app-node-platform"
|
||||
import { LayerNode } from "@opencode-ai/util/effect/layer-node"
|
||||
import { FSUtil } from "@opencode-ai/util/fs-util"
|
||||
|
|
@ -267,6 +267,33 @@ describe("FSUtil", () => {
|
|||
expect(result).toContain(path.join(tmp, "b.txt"))
|
||||
}),
|
||||
)
|
||||
|
||||
it(
|
||||
"stops at the first match when requested",
|
||||
Effect.gen(function* () {
|
||||
const filesys = yield* FileSystem.FileSystem
|
||||
const tmp = yield* filesys.makeTempDirectoryScoped()
|
||||
yield* filesys.writeFileString(path.join(tmp, "marker"), "root")
|
||||
const child = path.join(tmp, "sub")
|
||||
yield* filesys.makeDirectory(child)
|
||||
yield* filesys.writeFileString(path.join(child, "marker"), "child")
|
||||
const checked: string[] = []
|
||||
const instrumented = FileSystem.FileSystem.of({
|
||||
...filesys,
|
||||
exists: (target) => Effect.sync(() => checked.push(target)).pipe(Effect.andThen(filesys.exists(target))),
|
||||
})
|
||||
const search = yield* FSUtil.Service.pipe(
|
||||
Effect.provide(
|
||||
FSUtil.layer.pipe(Layer.fresh, Layer.provide(Layer.succeed(FileSystem.FileSystem, instrumented))),
|
||||
),
|
||||
)
|
||||
|
||||
expect(yield* search.up({ targets: ["marker", "other"], start: child, mode: "first" })).toEqual([
|
||||
path.join(child, "marker"),
|
||||
])
|
||||
expect(checked).toEqual([path.join(child, "marker")])
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
describe("glob", () => {
|
||||
|
|
|
|||
|
|
@ -28,6 +28,13 @@ export namespace FSUtil {
|
|||
readonly type: "file" | "directory" | "symlink" | "other"
|
||||
}
|
||||
|
||||
export interface UpOptions {
|
||||
readonly targets: string[]
|
||||
readonly start: string
|
||||
readonly stop?: string
|
||||
readonly mode?: "all" | "first"
|
||||
}
|
||||
|
||||
export interface Interface extends FileSystem.FileSystem {
|
||||
readonly isDir: (path: string) => Effect.Effect<boolean>
|
||||
readonly isFile: (path: string) => Effect.Effect<boolean>
|
||||
|
|
@ -40,7 +47,7 @@ export namespace FSUtil {
|
|||
readonly readDirectoryEntries: (path: string) => Effect.Effect<DirEntry[], Error>
|
||||
readonly resolve: (path: string) => Effect.Effect<string>
|
||||
readonly findUp: (target: string, start: string, stop?: string) => Effect.Effect<string[], Error>
|
||||
readonly up: (options: { targets: string[]; start: string; stop?: string }) => Effect.Effect<string[], Error>
|
||||
readonly up: (options: UpOptions) => Effect.Effect<string[], Error>
|
||||
readonly globUp: (pattern: string, start: string, stop?: string) => Effect.Effect<string[], Error>
|
||||
readonly scan: (pattern: string, options?: Glob.Options) => Effect.Effect<string[], Error>
|
||||
readonly globMatch: (pattern: string, filepath: string) => boolean
|
||||
|
|
@ -153,27 +160,16 @@ export namespace FSUtil {
|
|||
})
|
||||
})
|
||||
|
||||
const findUp = Effect.fn("FileSystem.findUp")(function* (target: string, start: string, stop?: string) {
|
||||
const result: string[] = []
|
||||
let current = start
|
||||
while (true) {
|
||||
const search = join(current, target)
|
||||
if (yield* fs.exists(search)) result.push(search)
|
||||
if (stop === current) break
|
||||
const parent = dirname(current)
|
||||
if (parent === current) break
|
||||
current = parent
|
||||
}
|
||||
return result
|
||||
})
|
||||
|
||||
const up = Effect.fn("FileSystem.up")(function* (options: { targets: string[]; start: string; stop?: string }) {
|
||||
const up = Effect.fn("FileSystem.up")(function* (options: UpOptions) {
|
||||
const result: string[] = []
|
||||
let current = options.start
|
||||
while (true) {
|
||||
for (const target of options.targets) {
|
||||
const search = join(current, target)
|
||||
if (yield* fs.exists(search)) result.push(search)
|
||||
if (yield* fs.exists(search)) {
|
||||
result.push(search)
|
||||
if (options.mode === "first") return result
|
||||
}
|
||||
}
|
||||
if (options.stop === current) break
|
||||
const parent = dirname(current)
|
||||
|
|
@ -183,6 +179,10 @@ export namespace FSUtil {
|
|||
return result
|
||||
})
|
||||
|
||||
const findUp = Effect.fn("FileSystem.findUp")((target: string, start: string, stop?: string) =>
|
||||
up({ targets: [target], start, stop }),
|
||||
)
|
||||
|
||||
const globUp = Effect.fn("FileSystem.globUp")(function* (pattern: string, start: string, stop?: string) {
|
||||
const result: string[] = []
|
||||
let current = start
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue