refactor(branch-dropdown): drop worktree parent-branch merge shortcut

Remove the parent-branch button shown beside the branch selector along
with its supporting API, command, service, model, and i18n strings.
Add migration m20260423 to drop the now-unused folder.parent_branch
column.
This commit is contained in:
xintaofei
2026-04-23 14:02:46 +08:00
parent 377ae6d8e6
commit 50c5b12d53
23 changed files with 38 additions and 128 deletions

View File

@@ -19,6 +19,7 @@ use tauri::Manager;
use crate::app_error::AppCommandError; use crate::app_error::AppCommandError;
#[cfg(feature = "tauri-runtime")] #[cfg(feature = "tauri-runtime")]
use crate::db::error::DbError; use crate::db::error::DbError;
#[cfg(feature = "tauri-runtime")]
use crate::db::service::folder_service; use crate::db::service::folder_service;
use crate::db::AppDatabase; use crate::db::AppDatabase;
use crate::models::GitCredentials; use crate::models::GitCredentials;
@@ -479,40 +480,6 @@ pub async fn add_folder_to_history(
folder_service::add_folder(&db.conn, &path).await folder_service::add_folder(&db.conn, &path).await
} }
pub(crate) async fn set_folder_parent_branch_core(
conn: &sea_orm::DatabaseConnection,
path: &str,
parent_branch: Option<String>,
) -> Result<(), AppCommandError> {
use crate::db::entities::folder;
use sea_orm::{ColumnTrait, EntityTrait, QueryFilter};
let row = folder::Entity::find()
.filter(folder::Column::Path.eq(path))
.filter(folder::Column::DeletedAt.is_null())
.one(conn)
.await
.map_err(|e| {
AppCommandError::database_error("Failed to query folder").with_detail(e.to_string())
})?;
if let Some(folder_model) = row {
folder_service::set_folder_parent_branch(conn, folder_model.id, parent_branch)
.await
.map_err(AppCommandError::from)?;
}
Ok(())
}
#[cfg(feature = "tauri-runtime")]
#[cfg_attr(feature = "tauri-runtime", tauri::command)]
pub async fn set_folder_parent_branch(
db: tauri::State<'_, AppDatabase>,
path: String,
parent_branch: Option<String>,
) -> Result<(), AppCommandError> {
set_folder_parent_branch_core(&db.conn, &path, parent_branch).await
}
#[cfg(feature = "tauri-runtime")] #[cfg(feature = "tauri-runtime")]
#[cfg_attr(feature = "tauri-runtime", tauri::command)] #[cfg_attr(feature = "tauri-runtime", tauri::command)]
pub async fn remove_folder_from_history( pub async fn remove_folder_from_history(

View File

@@ -15,7 +15,6 @@ pub struct Model {
pub updated_at: DateTimeUtc, pub updated_at: DateTimeUtc,
pub deleted_at: Option<DateTimeUtc>, pub deleted_at: Option<DateTimeUtc>,
pub is_open: bool, pub is_open: bool,
pub parent_branch: Option<String>,
pub sort_order: i32, pub sort_order: i32,
} }

View File

@@ -0,0 +1,35 @@
use sea_orm_migration::prelude::*;
#[derive(DeriveMigrationName)]
pub struct Migration;
#[async_trait::async_trait]
impl MigrationTrait for Migration {
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
manager
.alter_table(
Table::alter()
.table(Folder::Table)
.drop_column(Folder::ParentBranch)
.to_owned(),
)
.await
}
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
manager
.alter_table(
Table::alter()
.table(Folder::Table)
.add_column(ColumnDef::new(Folder::ParentBranch).string().null())
.to_owned(),
)
.await
}
}
#[derive(DeriveIden)]
enum Folder {
Table,
ParentBranch,
}

View File

@@ -11,6 +11,7 @@ mod m20260404_000001_model_provider;
mod m20260406_000001_agent_setting_model_provider; mod m20260406_000001_agent_setting_model_provider;
mod m20260420_000001_opened_tabs; mod m20260420_000001_opened_tabs;
mod m20260422_000001_folder_sort_order; mod m20260422_000001_folder_sort_order;
mod m20260423_000001_drop_folder_parent_branch;
pub struct Migrator; pub struct Migrator;
#[async_trait::async_trait] #[async_trait::async_trait]
@@ -28,6 +29,7 @@ impl MigratorTrait for Migrator {
Box::new(m20260406_000001_agent_setting_model_provider::Migration), Box::new(m20260406_000001_agent_setting_model_provider::Migration),
Box::new(m20260420_000001_opened_tabs::Migration), Box::new(m20260420_000001_opened_tabs::Migration),
Box::new(m20260422_000001_folder_sort_order::Migration), Box::new(m20260422_000001_folder_sort_order::Migration),
Box::new(m20260423_000001_drop_folder_parent_branch::Migration),
] ]
} }
} }

View File

@@ -31,7 +31,6 @@ fn to_detail(m: folder::Model) -> FolderDetail {
name: m.name, name: m.name,
path: m.path, path: m.path,
git_branch: m.git_branch, git_branch: m.git_branch,
parent_branch: m.parent_branch,
default_agent_type, default_agent_type,
last_opened_at: m.last_opened_at, last_opened_at: m.last_opened_at,
sort_order: m.sort_order, sort_order: m.sort_order,
@@ -85,7 +84,6 @@ pub async fn add_folder(
name: Set(name), name: Set(name),
path: Set(path.to_string()), path: Set(path.to_string()),
git_branch: Set(None), git_branch: Set(None),
parent_branch: Set(None),
default_agent_type: Set(None), default_agent_type: Set(None),
last_opened_at: Set(now), last_opened_at: Set(now),
created_at: Set(now), created_at: Set(now),
@@ -127,22 +125,6 @@ pub async fn remove_folder(conn: &DatabaseConnection, path: &str) -> Result<(),
Ok(()) Ok(())
} }
pub async fn set_folder_parent_branch(
conn: &DatabaseConnection,
folder_id: i32,
parent_branch: Option<String>,
) -> Result<(), DbError> {
let row = folder::Entity::find_by_id(folder_id).one(conn).await?;
if let Some(row) = row {
let mut active = row.into_active_model();
active.parent_branch = Set(parent_branch);
active.updated_at = Set(Utc::now());
active.update(conn).await?;
}
Ok(())
}
pub async fn set_folder_open( pub async fn set_folder_open(
conn: &DatabaseConnection, conn: &DatabaseConnection,
folder_id: i32, folder_id: i32,

View File

@@ -225,7 +225,6 @@ mod tauri_app {
folders::remove_folder_from_workspace, folders::remove_folder_from_workspace,
folders::reorder_folders, folders::reorder_folders,
folders::add_folder_to_history, folders::add_folder_to_history,
folders::set_folder_parent_branch,
folders::remove_folder_from_history, folders::remove_folder_from_history,
folders::create_folder_directory, folders::create_folder_directory,
folders::clone_repository, folders::clone_repository,

View File

@@ -17,7 +17,6 @@ pub struct FolderDetail {
pub name: String, pub name: String,
pub path: String, pub path: String,
pub git_branch: Option<String>, pub git_branch: Option<String>,
pub parent_branch: Option<String>,
pub default_agent_type: Option<AgentType>, pub default_agent_type: Option<AgentType>,
pub last_opened_at: DateTime<Utc>, pub last_opened_at: DateTime<Utc>,
pub sort_order: i32, pub sort_order: i32,

View File

@@ -287,13 +287,6 @@ pub async fn open_push_window(
})) }))
} }
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SetFolderParentBranchParams {
pub path: String,
pub parent_branch: Option<String>,
}
pub async fn add_folder_to_history( pub async fn add_folder_to_history(
Extension(state): Extension<Arc<AppState>>, Extension(state): Extension<Arc<AppState>>,
Json(params): Json<AddFolderParams>, Json(params): Json<AddFolderParams>,
@@ -305,16 +298,6 @@ pub async fn add_folder_to_history(
Ok(Json(result)) Ok(Json(result))
} }
pub async fn set_folder_parent_branch(
Extension(state): Extension<Arc<AppState>>,
Json(params): Json<SetFolderParentBranchParams>,
) -> Result<Json<()>, AppCommandError> {
let db = &state.db;
folder_commands::set_folder_parent_branch_core(&db.conn, &params.path, params.parent_branch)
.await?;
Ok(Json(()))
}
pub async fn remove_folder_from_history( pub async fn remove_folder_from_history(
Extension(state): Extension<Arc<AppState>>, Extension(state): Extension<Arc<AppState>>,
Json(params): Json<AddFolderParams>, Json(params): Json<AddFolderParams>,

View File

@@ -114,10 +114,6 @@ pub fn build_router(state: Arc<AppState>, token: String, static_dir: std::path::
"/add_folder_to_history", "/add_folder_to_history",
post(handlers::folders::add_folder_to_history), post(handlers::folders::add_folder_to_history),
) )
.route(
"/set_folder_parent_branch",
post(handlers::folders::set_folder_parent_branch),
)
.route( .route(
"/remove_folder_from_history", "/remove_folder_from_history",
post(handlers::folders::remove_folder_from_history), post(handlers::folders::remove_folder_from_history),

View File

@@ -5,7 +5,6 @@ import {
ArchiveRestore, ArchiveRestore,
Archive, Archive,
ArrowDownToLine, ArrowDownToLine,
ArrowLeftRight,
ChevronDown, ChevronDown,
ChevronRight, ChevronRight,
FolderGit2, FolderGit2,
@@ -77,7 +76,6 @@ import {
openCommitWindow, openCommitWindow,
openPushWindow, openPushWindow,
openStashWindow, openStashWindow,
setFolderParentBranch,
} from "@/lib/api" } from "@/lib/api"
import { openFileDialog, subscribe } from "@/lib/platform" import { openFileDialog, subscribe } from "@/lib/platform"
import { RemoteManageDialog } from "@/components/layout/remote-manage-dialog" import { RemoteManageDialog } from "@/components/layout/remote-manage-dialog"
@@ -130,7 +128,6 @@ export function BranchDropdown() {
const branch = activeFolder const branch = activeFolder
? (branches.get(activeFolder.id) ?? activeFolder.git_branch ?? null) ? (branches.get(activeFolder.id) ?? activeFolder.git_branch ?? null)
: null : null
const parentBranch = activeFolder?.parent_branch ?? null
const [branchList, setBranchList] = useState<GitBranchList>({ const [branchList, setBranchList] = useState<GitBranchList>({
local: [], local: [],
@@ -349,15 +346,9 @@ export function BranchDropdown() {
await runGitTask(t("tasks.newWorktree", { name }), async () => { await runGitTask(t("tasks.newWorktree", { name }), async () => {
await gitWorktreeAdd(folderPath, name, wtPath) await gitWorktreeAdd(folderPath, name, wtPath)
await openFolder(wtPath) await openFolder(wtPath)
await setFolderParentBranch(wtPath, branch)
}) })
} }
function handleMergeParent() {
if (!parentBranch) return
setConfirmAction({ type: "merge", branchName: parentBranch })
}
async function handleConfirm() { async function handleConfirm() {
if (!confirmAction) return if (!confirmAction) return
const { type, branchName } = confirmAction const { type, branchName } = confirmAction
@@ -818,18 +809,6 @@ export function BranchDropdown() {
</DropdownMenuContent> </DropdownMenuContent>
</DropdownMenu> </DropdownMenu>
{parentBranch && (
<button
className="flex cursor-default select-none items-center gap-1 rounded px-1.5 py-0.5 text-xs text-orange-500 transition-colors hover:bg-accent hover:text-orange-600 dark:text-orange-400 dark:hover:text-orange-300"
disabled={loading}
onClick={handleMergeParent}
title={t("parentBranchHint", { parentBranch })}
>
<ArrowLeftRight className="h-3 w-3 shrink-0" />
<span className="max-w-32 truncate">{parentBranch}</span>
</button>
)}
<AlertDialog <AlertDialog
open={confirmAction !== null} open={confirmAction !== null}
onOpenChange={(open) => { onOpenChange={(open) => {

View File

@@ -1082,7 +1082,6 @@
"noLocalBranches": "لا توجد فروع محلية", "noLocalBranches": "لا توجد فروع محلية",
"remoteBranches": "الفروع البعيدة ({count, plural, one {#} other {#}})", "remoteBranches": "الفروع البعيدة ({count, plural, one {#} other {#}})",
"noRemoteBranches": "لا توجد فروع بعيدة", "noRemoteBranches": "لا توجد فروع بعيدة",
"parentBranchHint": "تم إنشاء الفرع الحالي من {parentBranch}. انقر لدمج {parentBranch} في الفرع الحالي.",
"dialogs": { "dialogs": {
"newBranchTitle": "فرع جديد", "newBranchTitle": "فرع جديد",
"newBranchDescription": "إنشاء فرع جديد من الفرع الحالي {branch}", "newBranchDescription": "إنشاء فرع جديد من الفرع الحالي {branch}",

View File

@@ -1082,7 +1082,6 @@
"noLocalBranches": "Keine lokalen Branches", "noLocalBranches": "Keine lokalen Branches",
"remoteBranches": "Remote-Branches ({count, plural, one {#} other {#}})", "remoteBranches": "Remote-Branches ({count, plural, one {#} other {#}})",
"noRemoteBranches": "Keine Remote-Branches", "noRemoteBranches": "Keine Remote-Branches",
"parentBranchHint": "Der aktuelle Branch wurde von {parentBranch} erstellt. Klicken, um {parentBranch} in den aktuellen Branch zu mergen.",
"dialogs": { "dialogs": {
"newBranchTitle": "Neuer Branch", "newBranchTitle": "Neuer Branch",
"newBranchDescription": "Neuen Branch vom aktuellen Branch {branch} erstellen", "newBranchDescription": "Neuen Branch vom aktuellen Branch {branch} erstellen",

View File

@@ -1082,7 +1082,6 @@
"noLocalBranches": "No local branches", "noLocalBranches": "No local branches",
"remoteBranches": "Remote branches ({count, plural, one {#} other {#}})", "remoteBranches": "Remote branches ({count, plural, one {#} other {#}})",
"noRemoteBranches": "No remote branches", "noRemoteBranches": "No remote branches",
"parentBranchHint": "Current branch was created from {parentBranch}. Click to merge {parentBranch} into current branch.",
"dialogs": { "dialogs": {
"newBranchTitle": "New branch", "newBranchTitle": "New branch",
"newBranchDescription": "Create a new branch from current branch {branch}", "newBranchDescription": "Create a new branch from current branch {branch}",

View File

@@ -1082,7 +1082,6 @@
"noLocalBranches": "Sin ramas locales", "noLocalBranches": "Sin ramas locales",
"remoteBranches": "Ramas remotas ({count, plural, one {#} other {#}})", "remoteBranches": "Ramas remotas ({count, plural, one {#} other {#}})",
"noRemoteBranches": "Sin ramas remotas", "noRemoteBranches": "Sin ramas remotas",
"parentBranchHint": "La rama actual fue creada desde {parentBranch}. Haz clic para fusionar {parentBranch} en la rama actual.",
"dialogs": { "dialogs": {
"newBranchTitle": "Nueva rama", "newBranchTitle": "Nueva rama",
"newBranchDescription": "Crear una nueva rama desde la rama actual {branch}", "newBranchDescription": "Crear una nueva rama desde la rama actual {branch}",

View File

@@ -1082,7 +1082,6 @@
"noLocalBranches": "Aucune branche locale", "noLocalBranches": "Aucune branche locale",
"remoteBranches": "Branches distantes ({count, plural, one {#} other {#}})", "remoteBranches": "Branches distantes ({count, plural, one {#} other {#}})",
"noRemoteBranches": "Aucune branche distante", "noRemoteBranches": "Aucune branche distante",
"parentBranchHint": "La branche actuelle a été créée depuis {parentBranch}. Cliquez pour fusionner {parentBranch} dans la branche actuelle.",
"dialogs": { "dialogs": {
"newBranchTitle": "Nouvelle branche", "newBranchTitle": "Nouvelle branche",
"newBranchDescription": "Créer une nouvelle branche depuis la branche actuelle {branch}", "newBranchDescription": "Créer une nouvelle branche depuis la branche actuelle {branch}",

View File

@@ -1082,7 +1082,6 @@
"noLocalBranches": "ローカルブランチはありません", "noLocalBranches": "ローカルブランチはありません",
"remoteBranches": "リモートブランチ ({count, plural, one {#} other {#}})", "remoteBranches": "リモートブランチ ({count, plural, one {#} other {#}})",
"noRemoteBranches": "リモートブランチはありません", "noRemoteBranches": "リモートブランチはありません",
"parentBranchHint": "現在のブランチは {parentBranch} から作成されました。クリックして {parentBranch} を現在のブランチにマージします。",
"dialogs": { "dialogs": {
"newBranchTitle": "新規ブランチ", "newBranchTitle": "新規ブランチ",
"newBranchDescription": "現在のブランチ {branch} から新しいブランチを作成", "newBranchDescription": "現在のブランチ {branch} から新しいブランチを作成",

View File

@@ -1082,7 +1082,6 @@
"noLocalBranches": "로컬 브랜치가 없습니다", "noLocalBranches": "로컬 브랜치가 없습니다",
"remoteBranches": "원격 브랜치 ({count, plural, one {#} other {#}})", "remoteBranches": "원격 브랜치 ({count, plural, one {#} other {#}})",
"noRemoteBranches": "원격 브랜치가 없습니다", "noRemoteBranches": "원격 브랜치가 없습니다",
"parentBranchHint": "현재 브랜치는 {parentBranch}에서 생성되었습니다. 클릭하여 {parentBranch}를 현재 브랜치에 병합하세요.",
"dialogs": { "dialogs": {
"newBranchTitle": "새 브랜치", "newBranchTitle": "새 브랜치",
"newBranchDescription": "현재 브랜치 {branch}에서 새 브랜치를 만듭니다", "newBranchDescription": "현재 브랜치 {branch}에서 새 브랜치를 만듭니다",

View File

@@ -1082,7 +1082,6 @@
"noLocalBranches": "Sem branches locais", "noLocalBranches": "Sem branches locais",
"remoteBranches": "Branches remotas ({count, plural, one {#} other {#}})", "remoteBranches": "Branches remotas ({count, plural, one {#} other {#}})",
"noRemoteBranches": "Sem branches remotas", "noRemoteBranches": "Sem branches remotas",
"parentBranchHint": "A branch atual foi criada a partir de {parentBranch}. Clique para mesclar {parentBranch} na branch atual.",
"dialogs": { "dialogs": {
"newBranchTitle": "Nova branch", "newBranchTitle": "Nova branch",
"newBranchDescription": "Criar uma nova branch a partir da branch atual {branch}", "newBranchDescription": "Criar uma nova branch a partir da branch atual {branch}",

View File

@@ -1082,7 +1082,6 @@
"noLocalBranches": "无本地分支", "noLocalBranches": "无本地分支",
"remoteBranches": "远程分支 ({count})", "remoteBranches": "远程分支 ({count})",
"noRemoteBranches": "无远程分支", "noRemoteBranches": "无远程分支",
"parentBranchHint": "当前分支从 {parentBranch} 创建,点击合并 {parentBranch} 到当前分支",
"dialogs": { "dialogs": {
"newBranchTitle": "新建分支", "newBranchTitle": "新建分支",
"newBranchDescription": "从当前分支 {branch} 创建新分支", "newBranchDescription": "从当前分支 {branch} 创建新分支",

View File

@@ -1082,7 +1082,6 @@
"noLocalBranches": "無本地分支", "noLocalBranches": "無本地分支",
"remoteBranches": "遠端分支 ({count})", "remoteBranches": "遠端分支 ({count})",
"noRemoteBranches": "無遠端分支", "noRemoteBranches": "無遠端分支",
"parentBranchHint": "目前分支從 {parentBranch} 建立,點擊合併 {parentBranch} 到目前分支",
"dialogs": { "dialogs": {
"newBranchTitle": "新增分支", "newBranchTitle": "新增分支",
"newBranchDescription": "從目前分支 {branch} 建立新分支", "newBranchDescription": "從目前分支 {branch} 建立新分支",

View File

@@ -656,16 +656,6 @@ export async function getFolderConversation(
return getTransport().call("get_folder_conversation", { conversationId }) return getTransport().call("get_folder_conversation", { conversationId })
} }
export async function setFolderParentBranch(
path: string,
parentBranch: string | null
): Promise<void> {
return getTransport().call("set_folder_parent_branch", {
path,
parentBranch,
})
}
export async function removeFolderFromHistory(path: string): Promise<void> { export async function removeFolderFromHistory(path: string): Promise<void> {
return getTransport().call("remove_folder_from_history", { path }) return getTransport().call("remove_folder_from_history", { path })
} }

View File

@@ -524,16 +524,6 @@ export async function getFolderConversation(
return invoke("get_folder_conversation", { conversationId }) return invoke("get_folder_conversation", { conversationId })
} }
export async function setFolderParentBranch(
path: string,
parentBranch: string | null
): Promise<void> {
return invoke("set_folder_parent_branch", {
path,
parentBranch,
})
}
export async function removeFolderFromHistory(path: string): Promise<void> { export async function removeFolderFromHistory(path: string): Promise<void> {
return invoke("remove_folder_from_history", { path }) return invoke("remove_folder_from_history", { path })
} }

View File

@@ -160,7 +160,6 @@ export interface FolderDetail {
name: string name: string
path: string path: string
git_branch: string | null git_branch: string | null
parent_branch: string | null
default_agent_type: AgentType | null default_agent_type: AgentType | null
last_opened_at: string last_opened_at: string
sort_order: number sort_order: number