diff --git a/packages/core/src/git.ts b/packages/core/src/git.ts index 989e15ae3b..8802ca7317 100644 --- a/packages/core/src/git.ts +++ b/packages/core/src/git.ts @@ -17,6 +17,25 @@ export class Repository extends Schema.Class("Git.Repository")({ commonDirectory: AbsolutePath, }) {} +// Included from $GIT_DIR/config via include.path (git >= 1.7.10); OpenCode owns +// this file entirely, so updates are plain rewrites with no config parsing. +const snapshotConfigFile = "opencode.gitconfig" +const snapshotConfigInclude = `[include] + path = ${snapshotConfigFile} +` +const snapshotConfig = `[core] + autocrlf = false + longpaths = true + symlinks = true + fsmonitor = false + untrackedCache = true +[feature] + manyFiles = true +[index] + version = 4 + threads = true +` + export const ChangeSet = Schema.String.pipe(Schema.brand("Git.ChangeSet")) export type ChangeSet = typeof ChangeSet.Type @@ -376,19 +395,24 @@ const layer = Layer.effect( commonDirectory: input.gitDirectory, }) yield* repositoryOperation("create", repository, ["init"]) - yield* Effect.forEach( - [ - ["core.autocrlf", "false"], - ["core.longpaths", "true"], - ["core.symlinks", "true"], - ["core.fsmonitor", "false"], - ["feature.manyFiles", "true"], - ["index.version", "4"], - ["index.threads", "true"], - ["core.untrackedCache", "true"], - ], - ([key, value]) => repositoryOperation("create", repository, ["config", key, value]), - { discard: true }, + yield* Effect.gen(function* () { + yield* fs.writeFileString(path.join(input.gitDirectory, snapshotConfigFile), snapshotConfig) + const config = path.join(input.gitDirectory, "config") + const current = yield* fs.readFileString(config) + if (current.includes(snapshotConfigInclude)) return + yield* fs.writeFileString(config, `${current.endsWith("\n") ? "\n" : "\n\n"}${snapshotConfigInclude}`, { + flag: "a", + }) + }).pipe( + Effect.mapError( + (cause) => + new OperationError({ + operation: "create", + directory: input.gitDirectory, + message: "Failed to configure Git storage", + cause, + }), + ), ) if (!input.seed) return repository yield* fs.ensureDir(path.join(input.gitDirectory, "objects", "info")).pipe( diff --git a/packages/core/test/git.test.ts b/packages/core/test/git.test.ts index abcc60181f..055a6b7640 100644 --- a/packages/core/test/git.test.ts +++ b/packages/core/test/git.test.ts @@ -148,8 +148,21 @@ describe("Git trees", () => { const git = yield* Git.Service const source = yield* git.repo.discover(AbsolutePath.make(root.path)) if (!source) throw new Error("Repository not found") - const storage = AbsolutePath.make(path.join(root.path, ".snapshot")) + const storage = AbsolutePath.make(path.join(root.path, ".snapshot storage")) const repository = yield* git.repo.create({ worktree: source.worktree, gitDirectory: storage, seed: source }) + yield* Effect.promise(() => $`git --git-dir ${storage} config --add include.path first.gitconfig`.quiet()) + yield* Effect.promise(() => $`git --git-dir ${storage} config --add include.path second.gitconfig`.quiet()) + yield* Effect.promise(() => $`git --git-dir ${storage} config core.autocrlf true`.quiet()) + yield* git.repo.create({ worktree: source.worktree, gitDirectory: storage, seed: source }) + expect( + yield* Effect.promise(() => $`git --git-dir ${storage} config --local --includes core.autocrlf`.text()), + ).toBe("false\n") + expect( + (yield* Effect.promise(() => fs.readFile(path.join(storage, "config"), "utf8"))).match(/opencode\.gitconfig/g), + ).toHaveLength(1) + expect( + yield* Effect.promise(() => $`git --git-dir ${storage} config --local --get-all include.path`.text()), + ).toBe("opencode.gitconfig\nfirst.gitconfig\nsecond.gitconfig\n") yield* git.index.refresh({ repository, scope: RelativePath.make("scope") }) const before = yield* git.tree.write(repository)