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
74 changes: 69 additions & 5 deletions apps/desktop/src/lib/trpc/routers/projects/projects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,11 @@ import { PROJECT_COLOR_VALUES } from "shared/constants/project-colors";
import simpleGit from "simple-git";
import { z } from "zod";
import { publicProcedure, router } from "../..";
import { getDefaultBranch, getGitRoot } from "../workspaces/utils/git";
import {
getDefaultBranch,
getGitRoot,
refreshDefaultBranch,
} from "../workspaces/utils/git";
import { assignRandomColor } from "./utils/colors";

type Project = SelectProject;
Expand Down Expand Up @@ -253,10 +257,22 @@ export const createProjectsRouter = (getWindow: () => BrowserWindow | null) => {
branches = branchList.map((name) => ({ name, lastCommitDate: 0 }));
}

// Determine default branch
let defaultBranch = project.defaultBranch;
if (!defaultBranch) {
defaultBranch = await getDefaultBranch(project.mainRepoPath);
// Sync with remote in case the default branch changed (e.g. master -> main)
const remoteDefaultBranch = await refreshDefaultBranch(
project.mainRepoPath,
);

const defaultBranch =
remoteDefaultBranch ||
project.defaultBranch ||
(await getDefaultBranch(project.mainRepoPath));

if (defaultBranch !== project.defaultBranch) {
localDb
.update(projects)
.set({ defaultBranch })
.where(eq(projects.id, input.projectId))
.run();
}

// Sort: default branch first, then by date
Expand Down Expand Up @@ -581,6 +597,54 @@ export const createProjectsRouter = (getWindow: () => BrowserWindow | null) => {
return { success: true };
}),

refreshDefaultBranch: publicProcedure
.input(z.object({ id: z.string() }))
.mutation(async ({ input }) => {
const project = localDb
.select()
.from(projects)
.where(eq(projects.id, input.id))
.get();

if (!project) {
throw new Error(`Project ${input.id} not found`);
}

const remoteDefaultBranch = await refreshDefaultBranch(
project.mainRepoPath,
);

if (
remoteDefaultBranch &&
remoteDefaultBranch !== project.defaultBranch
) {
localDb
.update(projects)
.set({ defaultBranch: remoteDefaultBranch })
.where(eq(projects.id, input.id))
.run();

return {
success: true,
defaultBranch: remoteDefaultBranch,
changed: true,
previousBranch: project.defaultBranch,
};
}

// Ensure we always return a valid default branch
const defaultBranch =
project.defaultBranch ??
remoteDefaultBranch ??
(await getDefaultBranch(project.mainRepoPath));

return {
success: true,
defaultBranch,
changed: false,
};
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}),

close: publicProcedure
.input(z.object({ id: z.string() }))
.mutation(async ({ input }) => {
Expand Down
43 changes: 43 additions & 0 deletions apps/desktop/src/lib/trpc/routers/workspaces/utils/git.ts
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,49 @@ export async function fetchDefaultBranch(
return commit.trim();
}

/**
* Refreshes the local origin/HEAD symref from the remote and returns the current default branch.
* This detects when the remote repository's default branch has changed (e.g., master -> main).
* @param mainRepoPath - Path to the main repository
* @returns The current default branch name, or null if unable to determine
*/
export async function refreshDefaultBranch(
mainRepoPath: string,
): Promise<string | null> {
const git = simpleGit(mainRepoPath);

const hasRemote = await hasOriginRemote(mainRepoPath);
if (!hasRemote) {
return null;
}

try {
// Git doesn't auto-update origin/HEAD on fetch, so we must explicitly
// sync it to detect when the remote's default branch changes
await git.remote(["set-head", "origin", "--auto"]);

const headRef = await git.raw(["symbolic-ref", "refs/remotes/origin/HEAD"]);
const match = headRef.trim().match(/refs\/remotes\/origin\/(.+)/);
if (match) {
return match[1];
}
} catch {
// set-head requires network access; fall back to ls-remote which may
// work in some edge cases or provide a more specific error
try {
const result = await git.raw(["ls-remote", "--symref", "origin", "HEAD"]);
const symrefMatch = result.match(/ref:\s+refs\/heads\/(.+?)\tHEAD/);
if (symrefMatch) {
return symrefMatch[1];
}
} catch {
// Network unavailable - caller will use cached value
}
}

return null;
}

export async function checkNeedsRebase(
worktreePath: string,
defaultBranch: string,
Expand Down
33 changes: 24 additions & 9 deletions apps/desktop/src/lib/trpc/routers/workspaces/workspaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import {
hasUncommittedChanges,
hasUnpushedCommits,
listBranches,
refreshDefaultBranch,
removeWorktree,
safeCheckoutBranch,
worktreeExists,
Expand Down Expand Up @@ -67,19 +68,24 @@ export const createWorkspacesRouter = () => {
branch,
);

// Get default branch (lazy migration for existing projects without defaultBranch)
let defaultBranch = project.defaultBranch;
if (!defaultBranch) {
defaultBranch = await getDefaultBranch(project.mainRepoPath);
// Save it for future use
// Sync with remote in case the default branch changed (e.g. master -> main)
const remoteDefaultBranch = await refreshDefaultBranch(
project.mainRepoPath,
);

const defaultBranch =
remoteDefaultBranch ||
project.defaultBranch ||
(await getDefaultBranch(project.mainRepoPath));

if (defaultBranch !== project.defaultBranch) {
localDb
.update(projects)
.set({ defaultBranch })
.where(eq(projects.id, project.id))
.run();
}

// Use provided baseBranch or fall back to default
const targetBranch = input.baseBranch || defaultBranch;

// Check if this repo has a remote origin
Expand Down Expand Up @@ -1058,11 +1064,20 @@ export const createWorkspacesRouter = () => {
throw new Error(`Project ${workspace.projectId} not found`);
}

// Get default branch (lazy migration for existing projects without defaultBranch)
// Sync with remote in case the default branch changed (e.g. master -> main)
const remoteDefaultBranch = await refreshDefaultBranch(
project.mainRepoPath,
);

let defaultBranch = project.defaultBranch;
if (!defaultBranch) {
defaultBranch = await getDefaultBranch(project.mainRepoPath);
// Save it for future use
}
if (remoteDefaultBranch && remoteDefaultBranch !== defaultBranch) {
defaultBranch = remoteDefaultBranch;
}

if (defaultBranch !== project.defaultBranch) {
localDb
.update(projects)
.set({ defaultBranch })
Expand Down Expand Up @@ -1092,7 +1107,7 @@ export const createWorkspacesRouter = () => {
.where(eq(worktrees.id, worktree.id))
.run();

return { gitStatus };
return { gitStatus, defaultBranch };
}),

getGitHubStatus: publicProcedure
Expand Down
Loading