Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .superset/setup.sh
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ step_clone_local_db() {
return 0
fi

local source_db="$HOME/.superset-dev/local.db"
local source_db="$HOME/.superset/local.db"
local dest_dir="$HOME/.superset-${sanitized}"
local dest_db="$dest_dir/local.db"

Expand Down
90 changes: 1 addition & 89 deletions apps/desktop/src/lib/trpc/routers/changes/status.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
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";
Expand Down Expand Up @@ -29,16 +26,9 @@ export const createStatusRouter = () => {
const git = simpleGit(input.worktreePath);
const defaultBranch = input.defaultBranch || "main";

// First, get status (needed for subsequent operations)
// 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([
getBranchComparison(git, defaultBranch),
getTrackingBranchStatus(git),
Expand Down Expand Up @@ -101,81 +91,6 @@ 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[];
Expand Down Expand Up @@ -229,7 +144,6 @@ async function getBranchComparison(
return { commits, againstBase, ahead, behind };
}

/** Max file size for line counting (1 MiB) - skip larger files to avoid OOM */
const MAX_LINE_COUNT_SIZE = 1 * 1024 * 1024;

async function applyUntrackedLineCount(
Expand All @@ -245,9 +159,7 @@ async function applyUntrackedLineCount(
const lineCount = content.split("\n").length;
file.additions = lineCount;
file.deletions = 0;
} catch {
// Skip files that fail validation or reading
}
} catch {}
}
}

Expand Down
49 changes: 47 additions & 2 deletions apps/desktop/src/lib/trpc/routers/workspaces/procedures/status.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { workspaces } from "@superset/local-db";
import { and, eq, isNull } from "drizzle-orm";
import { workspaces, worktrees } from "@superset/local-db";
import { and, eq, isNull, not } from "drizzle-orm";
import { localDb } from "main/lib/local-db";
import { z } from "zod";
import { publicProcedure, router } from "../../..";
Expand Down Expand Up @@ -129,5 +129,50 @@ export const createStatusProcedures = () => {

return { success: true, workspaceId: input.workspaceId };
}),

syncBranch: publicProcedure
.input(
z.object({
workspaceId: z.string(),
branch: z.string(),
}),
)
.mutation(({ input }) => {
const { workspaceId, branch } = input;

if (!branch || branch === "HEAD") {
return { success: false as const, reason: "invalid-branch" as const };
}

const workspace = getWorkspaceNotDeleting(workspaceId);
if (!workspace) {
return { success: false as const, reason: "not-found" as const };
}

if (workspace.branch === branch) {
return { success: true as const, changed: false as const };
}

localDb
.update(workspaces)
.set({ branch })
.where(eq(workspaces.id, workspaceId))
.run();

if (workspace.worktreeId) {
localDb
.update(worktrees)
.set({ branch })
.where(
and(
eq(worktrees.id, workspace.worktreeId),
not(eq(worktrees.branch, branch)),
),
)
.run();
}

return { success: true as const, changed: true as const };
}),
});
};
Original file line number Diff line number Diff line change
@@ -1,12 +1,6 @@
import { useEffect } from "react";
import { useCallback, useEffect, useRef } from "react";
import { electronTrpc } from "renderer/lib/electron-trpc";

/**
* Invalidates workspace-related caches when the git branch (from status polling)
* diverges from the branch stored in the local DB workspace record.
*
* This keeps sidebar labels and hover cards in sync after external `git switch`.
*/
export function useBranchSyncInvalidation({
gitBranch,
workspaceBranch,
Expand All @@ -17,13 +11,53 @@ export function useBranchSyncInvalidation({
workspaceId: string;
}) {
const utils = electronTrpc.useUtils();
const { mutate } = electronTrpc.workspaces.syncBranch.useMutation();
const syncingRef = useRef<string | null>(null);

const doSync = useCallback(
(branch: string) => {
mutate(
{ workspaceId, branch },
{
onSuccess: (result) => {
if (!result.success || !("changed" in result) || !result.changed) {
syncingRef.current = null;
return;
}

utils.workspaces.getAllGrouped.setData(undefined, (oldData) => {
if (!oldData) return oldData;
return oldData.map((group) => ({
...group,
workspaces: group.workspaces.map((ws) =>
ws.id === workspaceId ? { ...ws, branch } : ws,
),
}));
});

utils.workspaces.get.invalidate({ id: workspaceId });
utils.workspaces.getWorktreeInfo.invalidate({
workspaceId,
});
},
onError: () => {
syncingRef.current = null;
},
},
);
},
[mutate, workspaceId, utils],
);

useEffect(() => {
if (!gitBranch || gitBranch === "HEAD" || !workspaceBranch) return;
if (gitBranch !== workspaceBranch) {
utils.workspaces.getAllGrouped.invalidate();
utils.workspaces.get.invalidate({ id: workspaceId });
utils.workspaces.getWorktreeInfo.invalidate({ workspaceId });
if (gitBranch === workspaceBranch) {
syncingRef.current = null;
return;
}
}, [gitBranch, workspaceBranch, workspaceId, utils]);
if (syncingRef.current === gitBranch) return;
syncingRef.current = gitBranch;

doSync(gitBranch);
}, [gitBranch, workspaceBranch, doSync]);
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}
1 change: 1 addition & 0 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.