From 9f3d124e04663c722cd94f317d0e46eaebc9d6f8 Mon Sep 17 00:00:00 2001 From: Wang Siyuan Date: Thu, 12 Mar 2026 16:05:41 +0800 Subject: [PATCH] fix: migrate sessions from global to git project after init Fixes #16812 The migrateFromGlobal function was being called before the project row was inserted into the database, causing a foreign key constraint violation. Additionally, it only ran when creating a new project row, not when an existing project transitioned from global to git-based ID. Changes: - Move migrateFromGlobal call to after project row insertion - Ensure migration runs even when project row already exists - Add test to verify session migration after git init Closes #16812 --- packages/opencode/src/project/project.ts | 8 ++- .../opencode/test/project/project.test.ts | 57 +++++++++++++++++++ 2 files changed, 62 insertions(+), 3 deletions(-) diff --git a/packages/opencode/src/project/project.ts b/packages/opencode/src/project/project.ts index 196dc8da619d..d3acefefbae7 100644 --- a/packages/opencode/src/project/project.ts +++ b/packages/opencode/src/project/project.ts @@ -230,9 +230,6 @@ export namespace Project { updated: Date.now(), }, } - if (data.id !== ProjectID.global) { - await migrateFromGlobal(data.id, data.worktree) - } return fresh }) @@ -277,6 +274,11 @@ export namespace Project { Database.use((db) => db.insert(ProjectTable).values(insert).onConflictDoUpdate({ target: ProjectTable.id, set: updateSet }).run(), ) + + if (data.id !== ProjectID.global) { + await migrateFromGlobal(data.id, data.worktree) + } + GlobalBus.emit("event", { payload: { type: Event.Updated.type, diff --git a/packages/opencode/test/project/project.test.ts b/packages/opencode/test/project/project.test.ts index cc6b8dde5b8b..edccdc410686 100644 --- a/packages/opencode/test/project/project.test.ts +++ b/packages/opencode/test/project/project.test.ts @@ -7,6 +7,11 @@ import { tmpdir } from "../fixture/fixture" import { Filesystem } from "../../src/util/filesystem" import { GlobalBus } from "../../src/bus/global" import { ProjectID } from "../../src/project/schema" +import { Session } from "../../src/session" +import { Instance } from "../../src/project/instance" +import { Database } from "../../src/storage/db" +import { SessionTable } from "../../src/session/session.sql" +import { eq } from "../../src/storage/db" Log.init({ print: false }) @@ -347,3 +352,55 @@ describe("Project.update", () => { expect(updated.commands?.start).toBe("make start") }) }) + +describe("Project session migration", () => { + test("migrates sessions from global to git project after git init", async () => { + await using tmp = await tmpdir() + + const globalProject = await Project.fromDirectory(tmp.path) + expect(globalProject.project.id).toBe(ProjectID.global) + + const sessions = await Instance.provide({ + directory: tmp.path, + fn: async () => { + const session1 = await Session.create({ title: "Session before git init" }) + const session2 = await Session.create({ title: "Another pre-git session" }) + + const globalSessions = [...Session.list({ roots: true })] + expect(globalSessions.length).toBe(2) + expect(globalSessions.map((s) => s.id)).toContain(session1.id) + expect(globalSessions.map((s) => s.id)).toContain(session2.id) + + return { session1, session2 } + }, + }) + + await $`git init`.cwd(tmp.path).quiet() + await $`git config user.email "test@test.com"`.cwd(tmp.path).quiet() + await $`git config user.name "Test User"`.cwd(tmp.path).quiet() + await $`git commit --allow-empty -m "initial commit"`.cwd(tmp.path).quiet() + + const gitProject = await Project.fromDirectory(tmp.path) + expect(gitProject.project.id).not.toBe(ProjectID.global) + expect(gitProject.project.vcs).toBe("git") + + const migratedRows = Database.use((db) => + db.select().from(SessionTable).where(eq(SessionTable.project_id, gitProject.project.id)).all(), + ) + expect(migratedRows.length).toBe(2) + + await Instance.reload({ + directory: tmp.path, + project: gitProject.project, + worktree: gitProject.project.worktree, + }) + + await Instance.provide({ + directory: tmp.path, + fn: async () => { + const gitSessions = [...Session.list({ roots: true })] + expect(gitSessions.length).toBe(2) + }, + }) + }) +})