Merge branch 'main-fix'

This commit is contained in:
xintaofei
2026-04-02 16:25:00 +08:00
16 changed files with 184 additions and 82 deletions

View File

@@ -1052,12 +1052,15 @@ pub async fn git_push(
path: String,
remote: Option<String>,
credentials: Option<GitCredentials>,
folder_id: Option<i32>,
db: tauri::State<'_, AppDatabase>,
) -> Result<GitPushResult, AppCommandError> {
let folder_id = window
.label()
.strip_prefix("push-")
.and_then(|value| value.parse::<i32>().ok());
let folder_id = folder_id.or_else(|| {
window
.label()
.strip_prefix("push-")
.and_then(|value| value.parse::<i32>().ok())
});
let data_dir = app.path().app_data_dir().map_err(|e| {
AppCommandError::external_command("Failed to resolve app data dir", e.to_string())
})?;
@@ -1658,11 +1661,14 @@ pub async fn git_commit(
path: String,
message: String,
files: Vec<String>,
folder_id: Option<i32>,
) -> Result<GitCommitResult, AppCommandError> {
let folder_id = window
.label()
.strip_prefix("commit-")
.and_then(|value| value.parse::<i32>().ok());
let folder_id = folder_id.or_else(|| {
window
.label()
.strip_prefix("commit-")
.and_then(|value| value.parse::<i32>().ok())
});
let emitter = EventEmitter::Tauri(app.clone());
git_commit_core(&emitter, folder_id, &db.conn, &path, &message, &files).await
}

View File

@@ -14,6 +14,7 @@ import { AppToaster } from "@/components/ui/app-toaster"
import { getFolder } from "@/lib/api"
import { toErrorMessage } from "@/lib/app-error"
import type { FolderDetail } from "@/lib/types"
import { GitCredentialProvider } from "@/contexts/git-credential-context"
const TOAST_DURATION_MS = 6000
@@ -85,45 +86,48 @@ function CommitPageInner() {
}, [pageTitle])
return (
<div className="flex h-screen flex-col overflow-hidden bg-background text-foreground">
<AppTitleBar
center={
<div className="text-sm font-semibold tracking-tight">
{t("title")}
{hasValidFolderId && folder ? ` · ${folder.name}` : ""}
</div>
}
/>
<GitCredentialProvider>
<div className="flex h-screen flex-col overflow-hidden bg-background text-foreground">
<AppTitleBar
center={
<div className="text-sm font-semibold tracking-tight">
{t("title")}
{hasValidFolderId && folder ? ` · ${folder.name}` : ""}
</div>
}
/>
<main className="flex-1 min-h-0 p-3">
{!hasValidFolderId ? (
<div className="rounded-lg border border-destructive/50 bg-destructive/10 px-3 py-2 text-sm text-destructive">
{t("invalidFolderId")}
</div>
) : loading ? (
<div className="flex h-full items-center justify-center text-sm text-muted-foreground">
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
{t("loadingRepo")}
</div>
) : error ? (
<div className="rounded-lg border border-destructive/50 bg-destructive/10 px-3 py-2 text-sm text-destructive">
{error}
</div>
) : folder ? (
<CommitWorkspace
folderPath={folder.path}
onCommitted={closeWindow}
onCancel={closeWindow}
/>
) : null}
</main>
<main className="flex-1 min-h-0 p-3">
{!hasValidFolderId ? (
<div className="rounded-lg border border-destructive/50 bg-destructive/10 px-3 py-2 text-sm text-destructive">
{t("invalidFolderId")}
</div>
) : loading ? (
<div className="flex h-full items-center justify-center text-sm text-muted-foreground">
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
{t("loadingRepo")}
</div>
) : error ? (
<div className="rounded-lg border border-destructive/50 bg-destructive/10 px-3 py-2 text-sm text-destructive">
{error}
</div>
) : folder ? (
<CommitWorkspace
folderPath={folder.path}
folderId={folder.id}
onCommitted={closeWindow}
onCancel={closeWindow}
/>
) : null}
</main>
<AppToaster
position="bottom-right"
duration={TOAST_DURATION_MS}
closeButton
/>
</div>
<AppToaster
position="bottom-right"
duration={TOAST_DURATION_MS}
closeButton
/>
</div>
</GitCredentialProvider>
)
}

View File

@@ -111,6 +111,7 @@ function PushPageInner() {
<PushWorkspace
folderPath={folder.path}
folderName={folder.name}
folderId={folder.id}
onPushed={closeWindow}
/>
) : null}

View File

@@ -1,7 +1,7 @@
"use client"
import { useCallback, useEffect, useMemo, useRef, useState } from "react"
import { Check, ChevronDown, ChevronRight, Loader2 } from "lucide-react"
import { Check, ChevronDown, ChevronRight, Loader2, Upload } from "lucide-react"
import { useTranslations } from "next-intl"
import { Button } from "@/components/ui/button"
import { ScrollArea } from "@/components/ui/scroll-area"
@@ -36,12 +36,23 @@ import {
import {
gitAddFiles,
gitCommit,
gitPush,
gitRollbackFile,
gitShowFile,
gitStatus,
deleteFileTreeEntry,
readFilePreview,
} from "@/lib/api"
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
import {
useGitCredential,
type GitRemoteHint,
} from "@/contexts/git-credential-context"
import type { GitStatusEntry } from "@/lib/types"
import { cn } from "@/lib/utils"
import { toast } from "sonner"
@@ -51,6 +62,7 @@ import { toErrorMessage } from "@/lib/app-error"
interface CommitWorkspaceProps {
folderPath: string
folderId?: number | null
onCommitted?: () => void
onCancel?: () => void
}
@@ -209,11 +221,13 @@ const CONFIRM_INITIAL: ConfirmState = {
export function CommitWorkspace({
folderPath,
folderId,
onCommitted,
onCancel,
}: CommitWorkspaceProps) {
const t = useTranslations("Folder.commitDialog")
const tCommon = useTranslations("Folder.common")
const { withCredentialRetry } = useGitCredential()
const [entries, setEntries] = useState<GitStatusEntry[]>([])
const containerRef = useRef<HTMLDivElement>(null)
const [containerWidth, setContainerWidth] = useState(0)
@@ -415,29 +429,49 @@ export function CommitWorkspace({
[filePathSet, handleViewDiff]
)
const handleCommit = useCallback(async () => {
const commitMessage = messageRef.current.trim()
if (!commitMessage || selected.size === 0 || !folderPath) return
setCommitting(true)
setError(null)
try {
const result = await gitCommit(
folderPath,
commitMessage,
Array.from(selected)
)
toast.success(t("toasts.commitCompleted"), {
description: t("toasts.committedFiles", {
count: result.committed_files,
}),
})
onCommitted?.()
} catch (err) {
setError(toErrorMessage(err))
} finally {
setCommitting(false)
}
}, [folderPath, onCommitted, selected, t])
const handleCommit = useCallback(
async (andPush?: boolean) => {
const commitMessage = messageRef.current.trim()
if (!commitMessage || selected.size === 0 || !folderPath) return
setCommitting(true)
setError(null)
try {
const result = await gitCommit(
folderPath,
commitMessage,
Array.from(selected),
folderId
)
toast.success(t("toasts.commitCompleted"), {
description: t("toasts.committedFiles", {
count: result.committed_files,
}),
})
if (andPush) {
try {
const hint: GitRemoteHint = { folderPath }
await withCredentialRetry(
(creds) => gitPush(folderPath, null, creds, folderId),
hint
)
} catch (pushErr) {
toast.error(t("toasts.pushFailed"), {
description: toErrorMessage(pushErr),
})
return
}
}
onCommitted?.()
} catch (err) {
setError(toErrorMessage(err))
} finally {
setCommitting(false)
}
},
[folderId, folderPath, onCommitted, selected, t, withCredentialRetry]
)
// --- Context menu actions ---
@@ -1168,15 +1202,42 @@ export function CommitWorkspace({
<Button variant="outline" onClick={onCancel}>
{tCommon("cancel")}
</Button>
<Button
disabled={committing || !hasMessage || selected.size === 0}
onClick={handleCommit}
>
{committing && (
<Loader2 className="mr-1 h-4 w-4 animate-spin" />
)}
{t("commitButton", { count: selected.size })}
</Button>
<div className="flex">
<Button
disabled={committing || !hasMessage || selected.size === 0}
onClick={() => handleCommit()}
className="rounded-r-none"
>
{committing && (
<Loader2 className="mr-1 h-4 w-4 animate-spin" />
)}
{t("commitButton", { count: selected.size })}
</Button>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
disabled={
committing || !hasMessage || selected.size === 0
}
className="rounded-l-none border-l border-l-primary-foreground/20 px-1.5"
>
<ChevronDown className="h-3.5 w-3.5" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem onClick={() => handleCommit()}>
<Check className="h-4 w-4" />
{t("commitButton", { count: selected.size })}
</DropdownMenuItem>
<DropdownMenuItem onClick={() => handleCommit(true)}>
<Upload className="h-4 w-4" />
{t("commitAndPushButton", {
count: selected.size,
})}
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
</div>
</div>
</div>

View File

@@ -278,12 +278,14 @@ function parseDate(dateStr: string): Date | null {
interface PushWorkspaceProps {
folderPath: string
folderName: string
folderId?: number | null
onPushed?: () => void
}
export function PushWorkspace({
folderPath,
folderName,
folderId,
onPushed,
}: PushWorkspaceProps) {
const t = useTranslations("Folder.pushWindow")
@@ -392,7 +394,7 @@ export function PushWorkspace({
)?.url
const hint: GitRemoteHint = remoteUrl ? { remoteUrl } : { folderPath }
await withCredentialRetry(
(creds) => gitPush(folderPath, selectedRemote, creds),
(creds) => gitPush(folderPath, selectedRemote, creds, folderId),
hint
)
onPushed?.()

View File

@@ -1009,6 +1009,7 @@
"commitDialog": {
"toasts": {
"commitCompleted": "اكتمل التزام الكود",
"pushFailed": "فشل الدفع",
"committedFiles": "{count, plural, one {# ملف تم الالتزام به} other {# ملفات تم الالتزام بها}}",
"addedToVcs": "تمت الإضافة إلى VCS",
"addToVcsFailed": "فشلت الإضافة إلى VCS",
@@ -1050,6 +1051,7 @@
"commitMessage": "رسالة الالتزام",
"commitMessagePlaceholder": "أدخل رسالة الالتزام...",
"commitButton": "التزام ({count})",
"commitAndPushButton": "إيداع ودفع ({count})",
"head": "HEAD",
"workingTree": "شجرة العمل",
"clickFileToDiff": "انقر اسم الملف لعرض الفرق",

View File

@@ -1009,6 +1009,7 @@
"commitDialog": {
"toasts": {
"commitCompleted": "Code-Commit abgeschlossen",
"pushFailed": "Push fehlgeschlagen",
"committedFiles": "{count, plural, one {# Datei committet} other {# Dateien committet}}",
"addedToVcs": "Zu VCS hinzugefügt",
"addToVcsFailed": "Hinzufügen zu VCS fehlgeschlagen",
@@ -1050,6 +1051,7 @@
"commitMessage": "Commit-Nachricht",
"commitMessagePlaceholder": "Commit-Nachricht eingeben...",
"commitButton": "Einchecken ({count})",
"commitAndPushButton": "Committen und pushen ({count})",
"head": "HEAD",
"workingTree": "Arbeitsverzeichnis",
"clickFileToDiff": "Dateinamen anklicken, um Diff zu sehen",

View File

@@ -1009,6 +1009,7 @@
"commitDialog": {
"toasts": {
"commitCompleted": "Code commit completed",
"pushFailed": "Push failed",
"committedFiles": "Committed {count, plural, one {# file} other {# files}}",
"addedToVcs": "Added to VCS",
"addToVcsFailed": "Failed to add to VCS",
@@ -1050,6 +1051,7 @@
"commitMessage": "Commit message",
"commitMessagePlaceholder": "Enter commit message...",
"commitButton": "Commit ({count})",
"commitAndPushButton": "Commit and Push ({count})",
"head": "HEAD",
"workingTree": "Working Tree",
"clickFileToDiff": "Click a file name to view diff",

View File

@@ -1009,6 +1009,7 @@
"commitDialog": {
"toasts": {
"commitCompleted": "Commit de código completado",
"pushFailed": "Error al enviar",
"committedFiles": "{count, plural, one {# archivo confirmado} other {# archivos confirmados}}",
"addedToVcs": "Añadido a VCS",
"addToVcsFailed": "No se pudo añadir a VCS",
@@ -1050,6 +1051,7 @@
"commitMessage": "Mensaje de commit",
"commitMessagePlaceholder": "Introduce el mensaje de commit...",
"commitButton": "Confirmar ({count})",
"commitAndPushButton": "Confirmar y enviar ({count})",
"head": "HEAD",
"workingTree": "Árbol de trabajo",
"clickFileToDiff": "Haz clic en un nombre de archivo para ver diff",

View File

@@ -1009,6 +1009,7 @@
"commitDialog": {
"toasts": {
"commitCompleted": "Commit de code terminé",
"pushFailed": "Échec du push",
"committedFiles": "{count, plural, one {# fichier commit} other {# fichiers commit}}",
"addedToVcs": "Ajouté à VCS",
"addToVcsFailed": "Échec de lajout à VCS",
@@ -1050,6 +1051,7 @@
"commitMessage": "Message de commit",
"commitMessagePlaceholder": "Saisissez le message de commit...",
"commitButton": "Valider ({count})",
"commitAndPushButton": "Valider et pousser ({count})",
"head": "HEAD",
"workingTree": "Arbre de travail",
"clickFileToDiff": "Cliquez sur un nom de fichier pour voir le diff",

View File

@@ -1009,6 +1009,7 @@
"commitDialog": {
"toasts": {
"commitCompleted": "コードコミットが完了しました",
"pushFailed": "プッシュに失敗しました",
"committedFiles": "{count, plural, one {# 個のファイルをコミット} other {# 個のファイルをコミット}}",
"addedToVcs": "VCS に追加しました",
"addToVcsFailed": "VCS への追加に失敗しました",
@@ -1050,6 +1051,7 @@
"commitMessage": "コミットメッセージ",
"commitMessagePlaceholder": "コミットメッセージを入力...",
"commitButton": "コミット ({count})",
"commitAndPushButton": "コミットしてプッシュ ({count})",
"head": "HEAD",
"workingTree": "作業ツリー",
"clickFileToDiff": "ファイル名をクリックして差分を表示",

View File

@@ -1009,6 +1009,7 @@
"commitDialog": {
"toasts": {
"commitCompleted": "코드 커밋이 완료되었습니다",
"pushFailed": "푸시 실패",
"committedFiles": "{count, plural, one {#개 파일 커밋됨} other {#개 파일 커밋됨}}",
"addedToVcs": "VCS에 추가되었습니다",
"addToVcsFailed": "VCS에 추가하지 못했습니다",
@@ -1050,6 +1051,7 @@
"commitMessage": "커밋 메시지",
"commitMessagePlaceholder": "커밋 메시지 입력...",
"commitButton": "커밋 ({count})",
"commitAndPushButton": "커밋 후 푸시 ({count})",
"head": "HEAD",
"workingTree": "작업 트리",
"clickFileToDiff": "파일 이름을 클릭해 diff를 확인하세요",

View File

@@ -1009,6 +1009,7 @@
"commitDialog": {
"toasts": {
"commitCompleted": "Commit de código concluído",
"pushFailed": "Falha no envio",
"committedFiles": "{count, plural, one {# arquivo commitado} other {# arquivos commitados}}",
"addedToVcs": "Adicionado ao VCS",
"addToVcsFailed": "Falha ao adicionar ao VCS",
@@ -1050,6 +1051,7 @@
"commitMessage": "Mensagem de commit",
"commitMessagePlaceholder": "Digite a mensagem de commit...",
"commitButton": "Confirmar ({count})",
"commitAndPushButton": "Confirmar e enviar ({count})",
"head": "HEAD",
"workingTree": "Árvore de trabalho",
"clickFileToDiff": "Clique no nome do arquivo para ver o diff",

View File

@@ -1009,6 +1009,7 @@
"commitDialog": {
"toasts": {
"commitCompleted": "提交代码完成",
"pushFailed": "推送失败",
"committedFiles": "已提交 {count} 个文件",
"addedToVcs": "已添加到 VCS",
"addToVcsFailed": "添加到 VCS 失败",
@@ -1050,6 +1051,7 @@
"commitMessage": "提交消息",
"commitMessagePlaceholder": "输入提交信息...",
"commitButton": "提交 ({count})",
"commitAndPushButton": "提交并推送 ({count})",
"head": "HEAD当前提交",
"workingTree": "工作区",
"clickFileToDiff": "点击文件名查看差异",

View File

@@ -1009,6 +1009,7 @@
"commitDialog": {
"toasts": {
"commitCompleted": "提交程式碼完成",
"pushFailed": "推送失敗",
"committedFiles": "已提交 {count} 個檔案",
"addedToVcs": "已加入到 VCS",
"addToVcsFailed": "加入到 VCS 失敗",
@@ -1050,6 +1051,7 @@
"commitMessage": "提交訊息",
"commitMessagePlaceholder": "輸入提交訊息...",
"commitButton": "提交 ({count})",
"commitAndPushButton": "提交並推送 ({count})",
"head": "HEAD目前提交",
"workingTree": "工作目錄",
"clickFileToDiff": "點擊檔案名稱查看差異",

View File

@@ -574,12 +574,14 @@ export async function gitPushInfo(path: string): Promise<GitPushInfo> {
export async function gitPush(
path: string,
remote?: string | null,
credentials?: GitCredentials | null
credentials?: GitCredentials | null,
folderId?: number | null
): Promise<GitPushResult> {
return getTransport().call("git_push", {
path,
remote: remote ?? null,
credentials: credentials ?? null,
folderId: folderId ?? null,
})
}
@@ -886,9 +888,15 @@ export async function gitIsTracked(
export async function gitCommit(
path: string,
message: string,
files: string[]
files: string[],
folderId?: number | null
): Promise<GitCommitResult> {
return getTransport().call("git_commit", { path, message, files })
return getTransport().call("git_commit", {
path,
message,
files,
folderId: folderId ?? null,
})
}
export async function gitRollbackFile(