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 { useRouter } from "@tanstack/react-router";
|
||||||
import { selectedChatIdAtom } from "@/atoms/chatAtoms";
|
import { selectedChatIdAtom } from "@/atoms/chatAtoms";
|
||||||
import { useChats } from "@/hooks/useChats";
|
import { useChats } from "@/hooks/useChats";
|
||||||
import { showError } from "@/lib/toast";
|
import { showError, showSuccess } from "@/lib/toast";
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import { useStreamChat } from "@/hooks/useStreamChat";
|
import { useStreamChat } from "@/hooks/useStreamChat";
|
||||||
import { useCurrentBranch } from "@/hooks/useCurrentBranch";
|
import { useCurrentBranch } from "@/hooks/useCurrentBranch";
|
||||||
import { useCheckoutVersion } from "@/hooks/useCheckoutVersion";
|
import { useCheckoutVersion } from "@/hooks/useCheckoutVersion";
|
||||||
|
import { useRenameBranch } from "@/hooks/useRenameBranch";
|
||||||
|
|
||||||
interface ChatHeaderProps {
|
interface ChatHeaderProps {
|
||||||
isVersionPaneOpen: boolean;
|
isVersionPaneOpen: boolean;
|
||||||
@@ -53,6 +54,7 @@ export function ChatHeader({
|
|||||||
} = useCurrentBranch(appId);
|
} = useCurrentBranch(appId);
|
||||||
|
|
||||||
const { checkoutVersion, isCheckingOutVersion } = useCheckoutVersion();
|
const { checkoutVersion, isCheckingOutVersion } = useCheckoutVersion();
|
||||||
|
const { renameBranch, isRenamingBranch } = useRenameBranch();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (appId) {
|
if (appId) {
|
||||||
@@ -65,6 +67,14 @@ export function ChatHeader({
|
|||||||
await checkoutVersion({ appId, versionId: "main" });
|
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 () => {
|
const handleNewChat = async () => {
|
||||||
if (appId) {
|
if (appId) {
|
||||||
try {
|
try {
|
||||||
@@ -127,14 +137,27 @@ export function ChatHeader({
|
|||||||
{branchInfoLoading && <span>Checking branch...</span>}
|
{branchInfoLoading && <span>Checking branch...</span>}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<Button
|
{currentBranchName === "master" ? (
|
||||||
variant="outline"
|
<Button
|
||||||
size="sm"
|
variant="outline"
|
||||||
onClick={handleCheckoutMainBranch}
|
size="sm"
|
||||||
disabled={isCheckingOutVersion || branchInfoLoading}
|
onClick={handleRenameMasterToMain}
|
||||||
>
|
disabled={isRenamingBranch || branchInfoLoading}
|
||||||
{isCheckingOutVersion ? "Checking out..." : "Switch to main branch"}
|
>
|
||||||
</Button>
|
{isRenamingBranch ? "Renaming..." : "Rename master to main"}
|
||||||
|
</Button>
|
||||||
|
) : (
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={handleCheckoutMainBranch}
|
||||||
|
disabled={isCheckingOutVersion || branchInfoLoading}
|
||||||
|
>
|
||||||
|
{isCheckingOutVersion
|
||||||
|
? "Checking out..."
|
||||||
|
: "Switch to main branch"}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
</div>
|
</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 { db, getDatabasePath } from "../../db";
|
||||||
import { apps, chats } from "../../db/schema";
|
import { apps, chats } from "../../db/schema";
|
||||||
import { desc, eq } from "drizzle-orm";
|
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 fs from "node:fs";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import { getDyadAppPath, getUserDataPath } from "../../paths/paths";
|
import { getDyadAppPath, getUserDataPath } from "../../paths/paths";
|
||||||
@@ -767,4 +767,55 @@ export function registerAppHandlers() {
|
|||||||
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
|
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
|
||||||
return { version: packageJson.version };
|
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,
|
ApproveProposalResult,
|
||||||
ImportAppResult,
|
ImportAppResult,
|
||||||
ImportAppParams,
|
ImportAppParams,
|
||||||
|
RenameBranchParams,
|
||||||
} from "./ipc_types";
|
} from "./ipc_types";
|
||||||
import type { ProposalResult } from "@/lib/schemas";
|
import type { ProposalResult } from "@/lib/schemas";
|
||||||
import { showError } from "@/lib/toast";
|
import { showError } from "@/lib/toast";
|
||||||
@@ -816,4 +817,8 @@ export class IpcClient {
|
|||||||
}): Promise<{ exists: boolean }> {
|
}): Promise<{ exists: boolean }> {
|
||||||
return this.ipcRenderer.invoke("check-app-name", params);
|
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;
|
appId: number;
|
||||||
chatId: number;
|
chatId: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface RenameBranchParams {
|
||||||
|
appId: number;
|
||||||
|
oldBranchName: string;
|
||||||
|
newBranchName: string;
|
||||||
|
}
|
||||||
|
|||||||
@@ -73,6 +73,7 @@ const validInvokeChannels = [
|
|||||||
"check-ai-rules",
|
"check-ai-rules",
|
||||||
"select-app-folder",
|
"select-app-folder",
|
||||||
"check-app-name",
|
"check-app-name",
|
||||||
|
"rename-branch",
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
// Add valid receive channels
|
// Add valid receive channels
|
||||||
|
|||||||
Reference in New Issue
Block a user