diff --git a/apps/desktop/src/lib/trpc/routers/changes/status.ts b/apps/desktop/src/lib/trpc/routers/changes/status.ts index 43334e8ae03..cc4b1fea638 100644 --- a/apps/desktop/src/lib/trpc/routers/changes/status.ts +++ b/apps/desktop/src/lib/trpc/routers/changes/status.ts @@ -1,3 +1,6 @@ +import { projects, workspaces, worktrees } from "@superset/local-db"; +import { and, eq, isNull, not } from "drizzle-orm"; +import { localDb } from "main/lib/local-db"; import type { ChangedFile, GitChangesStatus } from "shared/changes-types"; import simpleGit from "simple-git"; import { z } from "zod"; @@ -30,6 +33,10 @@ export const createStatusRouter = () => { // Use --no-optional-locks to avoid holding locks on the repository const status = await getStatusNoLock(input.worktreePath); const parsed = parseGitStatus(status); + syncWorkspaceBranch({ + worktreePath: input.worktreePath, + currentBranch: parsed.branch, + }); // Run independent operations in parallel const [branchComparison, trackingStatus] = await Promise.all([ @@ -94,6 +101,81 @@ export const createStatusRouter = () => { }); }; +/** + * Update local DB branch fields to match the current git branch for a worktree + * or main repo workspace path. + */ +function syncWorkspaceBranch({ + worktreePath, + currentBranch, +}: { + worktreePath: string; + currentBranch: string; +}): void { + if (!currentBranch || currentBranch === "HEAD") { + return; + } + + try { + const worktree = localDb + .select({ id: worktrees.id }) + .from(worktrees) + .where(eq(worktrees.path, worktreePath)) + .get(); + + if (worktree) { + localDb + .update(worktrees) + .set({ branch: currentBranch }) + .where( + and( + eq(worktrees.id, worktree.id), + not(eq(worktrees.branch, currentBranch)), + ), + ) + .run(); + + localDb + .update(workspaces) + .set({ branch: currentBranch }) + .where( + and( + eq(workspaces.worktreeId, worktree.id), + isNull(workspaces.deletingAt), + not(eq(workspaces.branch, currentBranch)), + ), + ) + .run(); + + return; + } + + const project = localDb + .select({ id: projects.id }) + .from(projects) + .where(eq(projects.mainRepoPath, worktreePath)) + .get(); + if (!project) { + return; + } + + localDb + .update(workspaces) + .set({ branch: currentBranch }) + .where( + and( + eq(workspaces.projectId, project.id), + eq(workspaces.type, "branch"), + isNull(workspaces.deletingAt), + not(eq(workspaces.branch, currentBranch)), + ), + ) + .run(); + } catch (error) { + console.warn("[changes/status] Failed to sync branch:", error); + } +} + interface BranchComparison { commits: GitChangesStatus["commits"]; againstBase: ChangedFile[]; diff --git a/apps/desktop/src/lib/trpc/routers/workspaces/procedures/git-status.ts b/apps/desktop/src/lib/trpc/routers/workspaces/procedures/git-status.ts index c17292d293b..1f978cb5200 100644 --- a/apps/desktop/src/lib/trpc/routers/workspaces/procedures/git-status.ts +++ b/apps/desktop/src/lib/trpc/routers/workspaces/procedures/git-status.ts @@ -131,9 +131,11 @@ export const createGitStatusProcedures = () => { // Extract worktree name from path (last segment) const worktreeName = worktree.path.split("/").pop() ?? worktree.branch; + const branchName = worktree.branch; return { worktreeName, + branchName, createdAt: worktree.createdAt, gitStatus: worktree.gitStatus ?? null, githubStatus: worktree.githubStatus ?? null, diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/WorkspaceListItem.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/WorkspaceListItem.tsx index 9b34144a33c..83d53e9cf29 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/WorkspaceListItem.tsx +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/WorkspaceListItem.tsx @@ -36,6 +36,7 @@ import { import { navigateToWorkspace } from "renderer/routes/_authenticated/_dashboard/utils/workspace-navigation"; import { AsciiSpinner } from "renderer/screens/main/components/AsciiSpinner"; import { StatusIndicator } from "renderer/screens/main/components/StatusIndicator"; +import { useBranchSyncInvalidation } from "renderer/screens/main/hooks/useBranchSyncInvalidation"; import { useWorkspaceRename } from "renderer/screens/main/hooks/useWorkspaceRename"; import { useTabsStore } from "renderer/stores/tabs/store"; import { extractPaneIdsFromLayout } from "renderer/stores/tabs/utils"; @@ -136,6 +137,12 @@ export function WorkspaceListItem({ }, ); + useBranchSyncInvalidation({ + gitBranch: localChanges?.branch, + workspaceBranch: branch, + workspaceId: id, + }); + // Calculate total local changes (staged + unstaged + untracked) const localDiffStats = useMemo(() => { if (!localChanges) return null; diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/components/WorkspaceHoverCard/WorkspaceHoverCard.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/components/WorkspaceHoverCard/WorkspaceHoverCard.tsx index 76942c2accc..aee13dfe0c2 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/components/WorkspaceHoverCard/WorkspaceHoverCard.tsx +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/components/WorkspaceHoverCard/WorkspaceHoverCard.tsx @@ -39,6 +39,7 @@ export function WorkspaceHoverCardContent({ const needsRebase = worktreeInfo?.gitStatus?.needsRebase; const worktreeName = worktreeInfo?.worktreeName; + const branchName = worktreeInfo?.branchName; const hasCustomAlias = workspaceAlias && worktreeName && workspaceAlias !== worktreeName; @@ -49,19 +50,19 @@ export function WorkspaceHoverCardContent({ {hasCustomAlias && (