diff --git a/src-tauri/src/commands/folders.rs b/src-tauri/src/commands/folders.rs index 638a4c6..55e6bdc 100644 --- a/src-tauri/src/commands/folders.rs +++ b/src-tauri/src/commands/folders.rs @@ -1052,12 +1052,15 @@ pub async fn git_push( path: String, remote: Option, credentials: Option, + folder_id: Option, db: tauri::State<'_, AppDatabase>, ) -> Result { - let folder_id = window - .label() - .strip_prefix("push-") - .and_then(|value| value.parse::().ok()); + let folder_id = folder_id.or_else(|| { + window + .label() + .strip_prefix("push-") + .and_then(|value| value.parse::().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, + folder_id: Option, ) -> Result { - let folder_id = window - .label() - .strip_prefix("commit-") - .and_then(|value| value.parse::().ok()); + let folder_id = folder_id.or_else(|| { + window + .label() + .strip_prefix("commit-") + .and_then(|value| value.parse::().ok()) + }); let emitter = EventEmitter::Tauri(app.clone()); git_commit_core(&emitter, folder_id, &db.conn, &path, &message, &files).await } diff --git a/src/app/commit/page.tsx b/src/app/commit/page.tsx index 4340f6f..ec80782 100644 --- a/src/app/commit/page.tsx +++ b/src/app/commit/page.tsx @@ -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 ( -
- - {t("title")} - {hasValidFolderId && folder ? ` · ${folder.name}` : ""} -
- } - /> + +
+ + {t("title")} + {hasValidFolderId && folder ? ` · ${folder.name}` : ""} +
+ } + /> -
- {!hasValidFolderId ? ( -
- {t("invalidFolderId")} -
- ) : loading ? ( -
- - {t("loadingRepo")} -
- ) : error ? ( -
- {error} -
- ) : folder ? ( - - ) : null} -
+
+ {!hasValidFolderId ? ( +
+ {t("invalidFolderId")} +
+ ) : loading ? ( +
+ + {t("loadingRepo")} +
+ ) : error ? ( +
+ {error} +
+ ) : folder ? ( + + ) : null} +
- - + + +
) } diff --git a/src/app/push/page.tsx b/src/app/push/page.tsx index 4771b90..72ecee1 100644 --- a/src/app/push/page.tsx +++ b/src/app/push/page.tsx @@ -111,6 +111,7 @@ function PushPageInner() { ) : null} diff --git a/src/components/layout/commit-dialog.tsx b/src/components/layout/commit-dialog.tsx index 8221458..759eff4 100644 --- a/src/components/layout/commit-dialog.tsx +++ b/src/components/layout/commit-dialog.tsx @@ -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([]) const containerRef = useRef(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({ - +
+ + + + + + + handleCommit()}> + + {t("commitButton", { count: selected.size })} + + handleCommit(true)}> + + {t("commitAndPushButton", { + count: selected.size, + })} + + + +
diff --git a/src/components/layout/push-workspace.tsx b/src/components/layout/push-workspace.tsx index ce0c324..d4e5337 100644 --- a/src/components/layout/push-workspace.tsx +++ b/src/components/layout/push-workspace.tsx @@ -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?.() diff --git a/src/i18n/messages/ar.json b/src/i18n/messages/ar.json index ef4b44b..ee58d27 100644 --- a/src/i18n/messages/ar.json +++ b/src/i18n/messages/ar.json @@ -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 --git a/src/i18n/messages/de.json b/src/i18n/messages/de.json index a9306b7..2820b0d 100644 --- a/src/i18n/messages/de.json +++ b/src/i18n/messages/de.json @@ -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", diff --git a/src/i18n/messages/en.json b/src/i18n/messages/en.json index 237da33..2d4de2c 100644 --- a/src/i18n/messages/en.json +++ b/src/i18n/messages/en.json @@ -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", diff --git a/src/i18n/messages/es.json b/src/i18n/messages/es.json index c2ee6c2..eef60ef 100644 --- a/src/i18n/messages/es.json +++ b/src/i18n/messages/es.json @@ -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", diff --git a/src/i18n/messages/fr.json b/src/i18n/messages/fr.json index 44844d1..7dd4719 100644 --- a/src/i18n/messages/fr.json +++ b/src/i18n/messages/fr.json @@ -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 l’ajout à 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", diff --git a/src/i18n/messages/ja.json b/src/i18n/messages/ja.json index 611ee68..ad65657 100644 --- a/src/i18n/messages/ja.json +++ b/src/i18n/messages/ja.json @@ -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 --git a/src/i18n/messages/ko.json b/src/i18n/messages/ko.json index a2e36cd..2d1f239 100644 --- a/src/i18n/messages/ko.json +++ b/src/i18n/messages/ko.json @@ -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를 확인하세요", diff --git a/src/i18n/messages/pt.json b/src/i18n/messages/pt.json index 9b87ffe..343034c 100644 --- a/src/i18n/messages/pt.json +++ b/src/i18n/messages/pt.json @@ -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", diff --git a/src/i18n/messages/zh-CN.json b/src/i18n/messages/zh-CN.json index 66170c6..59d7b48 100644 --- a/src/i18n/messages/zh-CN.json +++ b/src/i18n/messages/zh-CN.json @@ -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": "点击文件名查看差异", diff --git a/src/i18n/messages/zh-TW.json b/src/i18n/messages/zh-TW.json index 5355493..794746d 100644 --- a/src/i18n/messages/zh-TW.json +++ b/src/i18n/messages/zh-TW.json @@ -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": "點擊檔案名稱查看差異", diff --git a/src/lib/api.ts b/src/lib/api.ts index 9d95773..70d7ac8 100644 --- a/src/lib/api.ts +++ b/src/lib/api.ts @@ -574,12 +574,14 @@ export async function gitPushInfo(path: string): Promise { export async function gitPush( path: string, remote?: string | null, - credentials?: GitCredentials | null + credentials?: GitCredentials | null, + folderId?: number | null ): Promise { 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 { - return getTransport().call("git_commit", { path, message, files }) + return getTransport().call("git_commit", { + path, + message, + files, + folderId: folderId ?? null, + }) } export async function gitRollbackFile(