Rename branch (#199)
This is more important now that import app is available and not every git repo will be initialized with `main` as the default branch. This handles the other common case which is the `master` branch.
This commit is contained in:
@@ -20,11 +20,12 @@ import { IpcClient } from "@/ipc/ipc_client";
|
||||
import { useRouter } from "@tanstack/react-router";
|
||||
import { selectedChatIdAtom } from "@/atoms/chatAtoms";
|
||||
import { useChats } from "@/hooks/useChats";
|
||||
import { showError } from "@/lib/toast";
|
||||
import { showError, showSuccess } from "@/lib/toast";
|
||||
import { useEffect } from "react";
|
||||
import { useStreamChat } from "@/hooks/useStreamChat";
|
||||
import { useCurrentBranch } from "@/hooks/useCurrentBranch";
|
||||
import { useCheckoutVersion } from "@/hooks/useCheckoutVersion";
|
||||
import { useRenameBranch } from "@/hooks/useRenameBranch";
|
||||
|
||||
interface ChatHeaderProps {
|
||||
isVersionPaneOpen: boolean;
|
||||
@@ -53,6 +54,7 @@ export function ChatHeader({
|
||||
} = useCurrentBranch(appId);
|
||||
|
||||
const { checkoutVersion, isCheckingOutVersion } = useCheckoutVersion();
|
||||
const { renameBranch, isRenamingBranch } = useRenameBranch();
|
||||
|
||||
useEffect(() => {
|
||||
if (appId) {
|
||||
@@ -65,6 +67,14 @@ export function ChatHeader({
|
||||
await checkoutVersion({ appId, versionId: "main" });
|
||||
};
|
||||
|
||||
const handleRenameMasterToMain = async () => {
|
||||
if (!appId) return;
|
||||
// If this throws, it will automatically show an error toast
|
||||
await renameBranch({ oldBranchName: "master", newBranchName: "main" });
|
||||
|
||||
showSuccess("Master branch renamed to main");
|
||||
};
|
||||
|
||||
const handleNewChat = async () => {
|
||||
if (appId) {
|
||||
try {
|
||||
@@ -127,14 +137,27 @@ export function ChatHeader({
|
||||
{branchInfoLoading && <span>Checking branch...</span>}
|
||||
</span>
|
||||
</div>
|
||||
{currentBranchName === "master" ? (
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={handleRenameMasterToMain}
|
||||
disabled={isRenamingBranch || branchInfoLoading}
|
||||
>
|
||||
{isRenamingBranch ? "Renaming..." : "Rename master to main"}
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={handleCheckoutMainBranch}
|
||||
disabled={isCheckingOutVersion || branchInfoLoading}
|
||||
>
|
||||
{isCheckingOutVersion ? "Checking out..." : "Switch to main branch"}
|
||||
{isCheckingOutVersion
|
||||
? "Checking out..."
|
||||
: "Switch to main branch"}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
58
src/hooks/useRenameBranch.ts
Normal file
58
src/hooks/useRenameBranch.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import { IpcClient } from "@/ipc/ipc_client";
|
||||
import { showError } from "@/lib/toast";
|
||||
import { selectedAppIdAtom } from "@/atoms/appAtoms";
|
||||
import { useAtomValue } from "jotai";
|
||||
|
||||
interface RenameBranchParams {
|
||||
appId: number;
|
||||
oldBranchName: string;
|
||||
newBranchName: string;
|
||||
}
|
||||
|
||||
export function useRenameBranch() {
|
||||
const queryClient = useQueryClient();
|
||||
const currentAppId = useAtomValue(selectedAppIdAtom);
|
||||
|
||||
const mutation = useMutation<void, Error, RenameBranchParams>({
|
||||
mutationFn: async (params: RenameBranchParams) => {
|
||||
if (params.appId === null || params.appId === undefined) {
|
||||
throw new Error("App ID is required to rename a branch.");
|
||||
}
|
||||
if (!params.oldBranchName) {
|
||||
throw new Error("Old branch name is required.");
|
||||
}
|
||||
if (!params.newBranchName) {
|
||||
throw new Error("New branch name is required.");
|
||||
}
|
||||
await IpcClient.getInstance().renameBranch(params);
|
||||
},
|
||||
onSuccess: (_, variables) => {
|
||||
// Invalidate queries that depend on branch information
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: ["currentBranch", variables.appId],
|
||||
});
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: ["versions", variables.appId],
|
||||
});
|
||||
// Potentially show a success message or trigger other actions
|
||||
},
|
||||
meta: {
|
||||
showErrorToast: true,
|
||||
},
|
||||
});
|
||||
|
||||
const renameBranch = async (params: Omit<RenameBranchParams, "appId">) => {
|
||||
if (!currentAppId) {
|
||||
showError("No application selected.");
|
||||
return;
|
||||
}
|
||||
return mutation.mutateAsync({ ...params, appId: currentAppId });
|
||||
};
|
||||
|
||||
return {
|
||||
renameBranch,
|
||||
isRenamingBranch: mutation.isPending,
|
||||
renameBranchError: mutation.error,
|
||||
};
|
||||
}
|
||||
@@ -2,7 +2,7 @@ import { ipcMain } from "electron";
|
||||
import { db, getDatabasePath } from "../../db";
|
||||
import { apps, chats } from "../../db/schema";
|
||||
import { desc, eq } from "drizzle-orm";
|
||||
import type { App, CreateAppParams } from "../ipc_types";
|
||||
import type { App, CreateAppParams, RenameBranchParams } from "../ipc_types";
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { getDyadAppPath, getUserDataPath } from "../../paths/paths";
|
||||
@@ -767,4 +767,55 @@ export function registerAppHandlers() {
|
||||
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
|
||||
return { version: packageJson.version };
|
||||
});
|
||||
|
||||
handle("rename-branch", async (_, params: RenameBranchParams) => {
|
||||
const { appId, oldBranchName, newBranchName } = params;
|
||||
const app = await db.query.apps.findFirst({
|
||||
where: eq(apps.id, appId),
|
||||
});
|
||||
|
||||
if (!app) {
|
||||
throw new Error("App not found");
|
||||
}
|
||||
|
||||
const appPath = getDyadAppPath(app.path);
|
||||
|
||||
return withLock(appId, async () => {
|
||||
try {
|
||||
// Check if the old branch exists
|
||||
const branches = await git.listBranches({ fs, dir: appPath });
|
||||
if (!branches.includes(oldBranchName)) {
|
||||
throw new Error(`Branch '${oldBranchName}' not found.`);
|
||||
}
|
||||
|
||||
// Check if the new branch name already exists
|
||||
if (branches.includes(newBranchName)) {
|
||||
// If newBranchName is 'main' and oldBranchName is 'master',
|
||||
// and 'main' already exists, we might want to allow this if 'main' is the current branch
|
||||
// and just switch to it, or delete 'master'.
|
||||
// For now, let's keep it simple and throw an error.
|
||||
throw new Error(
|
||||
`Branch '${newBranchName}' already exists. Cannot rename.`,
|
||||
);
|
||||
}
|
||||
|
||||
await git.renameBranch({
|
||||
fs: fs,
|
||||
dir: appPath,
|
||||
oldref: oldBranchName,
|
||||
ref: newBranchName,
|
||||
});
|
||||
logger.info(
|
||||
`Branch renamed from '${oldBranchName}' to '${newBranchName}' for app ${appId}`,
|
||||
);
|
||||
} catch (error: any) {
|
||||
logger.error(
|
||||
`Failed to rename branch for app ${appId}: ${error.message}`,
|
||||
);
|
||||
throw new Error(
|
||||
`Failed to rename branch '${oldBranchName}' to '${newBranchName}': ${error.message}`,
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ import type {
|
||||
ApproveProposalResult,
|
||||
ImportAppResult,
|
||||
ImportAppParams,
|
||||
RenameBranchParams,
|
||||
} from "./ipc_types";
|
||||
import type { ProposalResult } from "@/lib/schemas";
|
||||
import { showError } from "@/lib/toast";
|
||||
@@ -816,4 +817,8 @@ export class IpcClient {
|
||||
}): Promise<{ exists: boolean }> {
|
||||
return this.ipcRenderer.invoke("check-app-name", params);
|
||||
}
|
||||
|
||||
public async renameBranch(params: RenameBranchParams): Promise<void> {
|
||||
await this.ipcRenderer.invoke("rename-branch", params);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -201,3 +201,9 @@ export interface ImportAppResult {
|
||||
appId: number;
|
||||
chatId: number;
|
||||
}
|
||||
|
||||
export interface RenameBranchParams {
|
||||
appId: number;
|
||||
oldBranchName: string;
|
||||
newBranchName: string;
|
||||
}
|
||||
|
||||
@@ -73,6 +73,7 @@ const validInvokeChannels = [
|
||||
"check-ai-rules",
|
||||
"select-app-folder",
|
||||
"check-app-name",
|
||||
"rename-branch",
|
||||
] as const;
|
||||
|
||||
// Add valid receive channels
|
||||
|
||||
Reference in New Issue
Block a user