chore: merge dev into v2 (#39290)
Co-authored-by: Luke Parker <10430890+Hona@users.noreply.github.com> Co-authored-by: opencode-agent[bot] <219766164+opencode-agent[bot]@users.noreply.github.com> Co-authored-by: Aarav Sareen <96787824+arvsrn@users.noreply.github.com> Co-authored-by: Brendan Allan <14191578+Brendonovich@users.noreply.github.com> Co-authored-by: opencode-agent[bot] <opencode-agent[bot]@users.noreply.github.com> Co-authored-by: Aiden Cline <rekram1-node@users.noreply.github.com> Co-authored-by: opencode <opencode@sst.dev> Co-authored-by: Jay <53023+jayair@users.noreply.github.com> Co-authored-by: David Hill <1879069+iamdavidhill@users.noreply.github.com> Co-authored-by: BB84 <110078428+BB-84C@users.noreply.github.com> Co-authored-by: Aiden Cline <aidenpcline@gmail.com> Co-authored-by: Dax <mail@thdxr.com> Co-authored-by: Brendan Allan <git@brendonovich.dev> Co-authored-by: Jay V <air@live.ca> Co-authored-by: Adam <2363879+adamdotdevin@users.noreply.github.com> Co-authored-by: Aiden Cline <63023139+rekram1-node@users.noreply.github.com> Co-authored-by: Dustin Deus <deusdustin@gmail.com> Co-authored-by: Frank <frank@anoma.ly> Co-authored-by: usrnk1 <7547651+usrnk1@users.noreply.github.com> Co-authored-by: Jack <jack@anoma.ly> Co-authored-by: Sebastian <hasta84@gmail.com> Co-authored-by: Jérôme Benoit <jerome.benoit@sap.com> Co-authored-by: Test User <test@test.com> Co-authored-by: Simon Klee <hello@simonklee.dk> Co-authored-by: Rahul A Mistry <149420892+ProdigyRahul@users.noreply.github.com> Co-authored-by: Qiping Li <liqiping1991@gmail.com> Co-authored-by: liqiping <liqiping@msh.team> Co-authored-by: OpeOginni <107570612+OpeOginni@users.noreply.github.com> Co-authored-by: Matthias Reso <13337103+mreso@users.noreply.github.com> Co-authored-by: tobwen <1864057+tobwen@users.noreply.github.com> Co-authored-by: Daniel Polito <danielbpolito@gmail.com> Co-authored-by: opencode <noreply@opencode.ai> Co-authored-by: Devin R Leopold <devin.leopold@gmail.com> Co-authored-by: Zach Bruggeman <mail@bruggie.com> Co-authored-by: Zach Bruggeman <zbruggeman@ramp.com> Co-authored-by: Kit Langton <kit.langton@gmail.com> Co-authored-by: David Siewert <david1gruppenplan@gmail.com> Co-authored-by: Andrei Dziahel <develop7@develop7.info> Co-authored-by: adityachaudhary99 <adityaachaudhary2003@gmail.com> Co-authored-by: Vladimir Glafirov <vglafirov@gitlab.com> Co-authored-by: Matt Carey <mcarey@cloudflare.com>
This commit is contained in:
parent
7211c9934a
commit
302e9b45ab
263 changed files with 12516 additions and 4822 deletions
|
|
@ -59,7 +59,6 @@ const layer = Layer.effect(
|
|||
finalize: (draft) =>
|
||||
Effect.gen(function* () {
|
||||
materialized.clear()
|
||||
const seen = new Map<string, string | undefined>()
|
||||
for (const [name, source] of draft.list()) {
|
||||
if (source.type === "local") {
|
||||
materialized.set(
|
||||
|
|
@ -83,14 +82,11 @@ const layer = Layer.effect(
|
|||
continue
|
||||
}
|
||||
}
|
||||
const target = Repository.cachePath(global.repos, repository)
|
||||
if (seen.has(target) && seen.get(target) !== source.branch) continue
|
||||
seen.set(target, source.branch)
|
||||
materialized.set(
|
||||
name,
|
||||
Info.make({
|
||||
name,
|
||||
path: AbsolutePath.make(target),
|
||||
path: AbsolutePath.make(Repository.cachePath(global.repos, repository, source.branch)),
|
||||
...(source.description === undefined ? {} : { description: source.description }),
|
||||
...(source.hidden === undefined ? {} : { hidden: source.hidden }),
|
||||
source,
|
||||
|
|
|
|||
|
|
@ -1,3 +1,10 @@
|
|||
/**
|
||||
* Local tracking checkouts for remote Git references, one per remote and
|
||||
* branch. Each checkout permanently tracks a single ref: the requested branch
|
||||
* when the cache key has one, otherwise the remote default branch. Content
|
||||
* follows "newest wins": refresh fetches and hard-resets, so readers may
|
||||
* observe the checkout move underneath them.
|
||||
*/
|
||||
import path from "path"
|
||||
import { Context, Effect, Layer, Schema } from "effect"
|
||||
import { FSUtil } from "@opencode-ai/util/fs-util"
|
||||
|
|
@ -135,7 +142,7 @@ const layer: Layer.Layer<Service, never, FSUtil.Service | Git.Service | EffectFl
|
|||
if (input.branch) yield* validateBranch(input.branch)
|
||||
|
||||
const repository = input.reference.label
|
||||
const localPath = Repository.cachePath(global.repos, input.reference)
|
||||
const localPath = Repository.cachePath(global.repos, input.reference, input.branch)
|
||||
const cloneTarget = Repository.parse(input.reference.remote) ?? input.reference
|
||||
|
||||
return yield* flock
|
||||
|
|
@ -143,21 +150,28 @@ const layer: Layer.Layer<Service, never, FSUtil.Service | Git.Service | EffectFl
|
|||
Effect.gen(function* () {
|
||||
yield* cacheOperation(fs.ensureDir(path.dirname(localPath)), "ensure cache directory", localPath)
|
||||
|
||||
const exists = yield* fs.existsSafe(localPath)
|
||||
const existing = yield* git.repo.discover(AbsolutePath.make(localPath))
|
||||
const origin = existing ? yield* git.remote.get(existing) : undefined
|
||||
const originReference = origin ? Repository.parse(origin) : undefined
|
||||
const reuse = Boolean(existing && originReference && Repository.same(originReference, cloneTarget))
|
||||
if (exists && !reuse) {
|
||||
// Discovery walks upward, so an enclosing repository with a
|
||||
// matching origin could masquerade as the cache entry; reuse
|
||||
// requires the checkout to live exactly at the cache path.
|
||||
const worktree = existing ? yield* fs.resolve(localPath) : undefined
|
||||
const reuse = Boolean(
|
||||
existing &&
|
||||
existing.worktree === worktree &&
|
||||
originReference &&
|
||||
Repository.same(originReference, cloneTarget),
|
||||
)
|
||||
if (!reuse && (yield* fs.existsSafe(localPath))) {
|
||||
yield* cacheOperation(fs.remove(localPath, { recursive: true }), "remove stale cache", localPath)
|
||||
}
|
||||
|
||||
const currentBranch = reuse && existing ? yield* git.history.branch(existing) : undefined
|
||||
const status = statusForRepository({
|
||||
reuse,
|
||||
refresh: input.refresh,
|
||||
branchMatches: input.branch ? currentBranch === input.branch : undefined,
|
||||
})
|
||||
const status = !reuse
|
||||
? ("cloned" as const)
|
||||
: input.refresh
|
||||
? ("refreshed" as const)
|
||||
: ("cached" as const)
|
||||
|
||||
if (status === "cloned") {
|
||||
yield* git.repo
|
||||
|
|
@ -177,25 +191,28 @@ const layer: Layer.Layer<Service, never, FSUtil.Service | Git.Service | EffectFl
|
|||
.pipe(Effect.mapError((error) => new FetchFailedError({ repository, message: error.message })))
|
||||
|
||||
if (input.branch) {
|
||||
const requestedBranch = input.branch
|
||||
yield* git.sync
|
||||
.fetchBranch(existing, { branch: requestedBranch })
|
||||
.fetchBranch(existing, { branch: input.branch })
|
||||
.pipe(Effect.mapError((error) => new FetchFailedError({ repository, message: error.message })))
|
||||
|
||||
yield* git.sync.checkoutRemoteBranch(existing, { branch: requestedBranch }).pipe(
|
||||
Effect.mapError(
|
||||
(error) =>
|
||||
new CheckoutFailedError({
|
||||
repository,
|
||||
branch: requestedBranch,
|
||||
message: error.message,
|
||||
}),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
// Checking out the tracked ref before resetting keeps the
|
||||
// checkout self-healing even if it was left on another
|
||||
// branch.
|
||||
const branch = input.branch ?? (yield* git.history.defaultRemoteBranch(existing))
|
||||
if (branch) {
|
||||
yield* git.sync
|
||||
.checkoutRemoteBranch(existing, { branch })
|
||||
.pipe(
|
||||
Effect.mapError(
|
||||
(error) => new CheckoutFailedError({ repository, branch, message: error.message }),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
const target = branch ?? (yield* git.history.branch(existing))
|
||||
yield* git.sync
|
||||
.resetHard(existing, yield* resetTarget(git, existing, input.branch))
|
||||
.resetHard(existing, target ? `origin/${target}` : "HEAD")
|
||||
.pipe(Effect.mapError((error) => new ResetFailedError({ repository, message: error.message })))
|
||||
}
|
||||
|
||||
|
|
@ -229,12 +246,6 @@ export const node = makeGlobalNode({
|
|||
deps: [EffectFlock.node, FSUtil.node, Git.node, Global.node],
|
||||
})
|
||||
|
||||
function statusForRepository(input: { reuse: boolean; refresh?: boolean; branchMatches?: boolean }) {
|
||||
if (!input.reuse) return "cloned" as const
|
||||
if (input.branchMatches === false || input.refresh) return "refreshed" as const
|
||||
return "cached" as const
|
||||
}
|
||||
|
||||
function errorMessage(error: unknown) {
|
||||
return error instanceof globalThis.Error ? error.message : String(error)
|
||||
}
|
||||
|
|
@ -245,17 +256,4 @@ function cacheOperation<A, E, R>(effect: Effect.Effect<A, E, R>, operation: stri
|
|||
)
|
||||
}
|
||||
|
||||
const resetTarget = Effect.fnUntraced(function* (
|
||||
git: Git.Interface,
|
||||
repository: Git.Repository,
|
||||
requestedBranch?: string,
|
||||
) {
|
||||
if (requestedBranch) return `origin/${requestedBranch}`
|
||||
const remoteHead = yield* git.history.defaultRemoteBranch(repository)
|
||||
if (remoteHead) return `origin/${remoteHead}`
|
||||
const currentBranch = yield* git.history.branch(repository)
|
||||
if (currentBranch) return `origin/${currentBranch}`
|
||||
return "HEAD"
|
||||
})
|
||||
|
||||
export * as RepositoryCache from "./repository-cache"
|
||||
|
|
|
|||
|
|
@ -118,8 +118,14 @@ export function isRemote(reference: Reference): reference is RemoteReference {
|
|||
return !isFile(reference)
|
||||
}
|
||||
|
||||
export function cachePath(root: string, reference: Reference): string {
|
||||
return path.join(root, ...reference.host.split(":"), ...reference.segments)
|
||||
/**
|
||||
* Checkouts are keyed by remote and branch: a branch-specific reference gets
|
||||
* its own directory so branchless refreshes can never move it. The branch is
|
||||
* percent-encoded because valid branch names may contain `/`.
|
||||
*/
|
||||
export function cachePath(root: string, reference: Reference, branch?: string): string {
|
||||
const base = path.join(root, ...reference.host.split(":"), ...reference.segments)
|
||||
return branch ? `${base}@${encodeURIComponent(branch)}` : base
|
||||
}
|
||||
|
||||
export function cacheIdentity(reference: Reference): string {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue