初步支持远程Git管理
This commit is contained in:
@@ -54,6 +54,12 @@ pub struct GitCommitResult {
|
||||
pub committed_files: usize,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct GitRemote {
|
||||
pub name: String,
|
||||
pub url: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
struct GitCommitSucceededEvent {
|
||||
folder_id: i32,
|
||||
@@ -1100,6 +1106,111 @@ pub async fn git_list_all_branches(path: String) -> Result<GitBranchList, AppCom
|
||||
})
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn git_list_remotes(path: String) -> Result<Vec<GitRemote>, AppCommandError> {
|
||||
let output = crate::process::tokio_command("git")
|
||||
.args(["remote", "-v"])
|
||||
.current_dir(&path)
|
||||
.output()
|
||||
.await
|
||||
.map_err(AppCommandError::io)?;
|
||||
|
||||
if !output.status.success() {
|
||||
return Err(git_command_error("remote -v", &output.stderr));
|
||||
}
|
||||
|
||||
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||
let mut seen = HashSet::new();
|
||||
let mut remotes = Vec::new();
|
||||
for line in stdout.lines() {
|
||||
// Format: "name\turl (fetch|push)"
|
||||
if !line.ends_with("(fetch)") {
|
||||
continue;
|
||||
}
|
||||
let Some((name, rest)) = line.split_once('\t') else {
|
||||
continue;
|
||||
};
|
||||
let url = rest.trim_end_matches("(fetch)").trim();
|
||||
if seen.insert(name.to_string()) {
|
||||
remotes.push(GitRemote {
|
||||
name: name.to_string(),
|
||||
url: url.to_string(),
|
||||
});
|
||||
}
|
||||
}
|
||||
Ok(remotes)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn git_fetch_remote(path: String, name: String) -> Result<String, AppCommandError> {
|
||||
let output = crate::process::tokio_command("git")
|
||||
.args(["fetch", &name])
|
||||
.current_dir(&path)
|
||||
.env("GIT_TERMINAL_PROMPT", "0")
|
||||
.stdin(std::process::Stdio::null())
|
||||
.output()
|
||||
.await
|
||||
.map_err(AppCommandError::io)?;
|
||||
|
||||
if !output.status.success() {
|
||||
return Err(git_command_error("fetch", &output.stderr));
|
||||
}
|
||||
Ok(String::from_utf8_lossy(&output.stderr).trim().to_string())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn git_add_remote(
|
||||
path: String,
|
||||
name: String,
|
||||
url: String,
|
||||
) -> Result<(), AppCommandError> {
|
||||
let output = crate::process::tokio_command("git")
|
||||
.args(["remote", "add", &name, &url])
|
||||
.current_dir(&path)
|
||||
.output()
|
||||
.await
|
||||
.map_err(AppCommandError::io)?;
|
||||
|
||||
if !output.status.success() {
|
||||
return Err(git_command_error("remote add", &output.stderr));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn git_remove_remote(path: String, name: String) -> Result<(), AppCommandError> {
|
||||
let output = crate::process::tokio_command("git")
|
||||
.args(["remote", "remove", &name])
|
||||
.current_dir(&path)
|
||||
.output()
|
||||
.await
|
||||
.map_err(AppCommandError::io)?;
|
||||
|
||||
if !output.status.success() {
|
||||
return Err(git_command_error("remote remove", &output.stderr));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn git_set_remote_url(
|
||||
path: String,
|
||||
name: String,
|
||||
url: String,
|
||||
) -> Result<(), AppCommandError> {
|
||||
let output = crate::process::tokio_command("git")
|
||||
.args(["remote", "set-url", &name, &url])
|
||||
.current_dir(&path)
|
||||
.output()
|
||||
.await
|
||||
.map_err(AppCommandError::io)?;
|
||||
|
||||
if !output.status.success() {
|
||||
return Err(git_command_error("remote set-url", &output.stderr));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn git_merge(
|
||||
path: String,
|
||||
|
||||
@@ -199,6 +199,11 @@ pub fn run() {
|
||||
folders::git_rollback_file,
|
||||
folders::git_add_files,
|
||||
folders::git_list_all_branches,
|
||||
folders::git_list_remotes,
|
||||
folders::git_fetch_remote,
|
||||
folders::git_add_remote,
|
||||
folders::git_remove_remote,
|
||||
folders::git_set_remote_url,
|
||||
folders::git_merge,
|
||||
folders::git_rebase,
|
||||
folders::git_delete_branch,
|
||||
|
||||
@@ -21,6 +21,7 @@ import {
|
||||
FolderGit2,
|
||||
FolderOpen,
|
||||
ArrowLeftRight,
|
||||
Globe,
|
||||
} from "lucide-react"
|
||||
import {
|
||||
DropdownMenu,
|
||||
@@ -80,6 +81,7 @@ import {
|
||||
openCommitWindow,
|
||||
setFolderParentBranch,
|
||||
} from "@/lib/tauri"
|
||||
import { RemoteManageDialog } from "@/components/layout/remote-manage-dialog"
|
||||
import { disposeTauriListener } from "@/lib/tauri-listener"
|
||||
import type { GitBranchList } from "@/lib/types"
|
||||
import { toast } from "sonner"
|
||||
@@ -131,11 +133,24 @@ export function BranchDropdown({
|
||||
const [worktreeOpen, setWorktreeOpen] = useState(false)
|
||||
const [worktreeBranchName, setWorktreeBranchName] = useState("")
|
||||
const [worktreePath, setWorktreePath] = useState("")
|
||||
const [manageRemotesOpen, setManageRemotesOpen] = useState(false)
|
||||
const taskSeq = useRef(0)
|
||||
const worktreeBranchSet = useMemo(
|
||||
() => new Set(branchList.worktree_branches),
|
||||
[branchList.worktree_branches]
|
||||
)
|
||||
const groupedRemoteBranches = useMemo(() => {
|
||||
const groups: Record<string, string[]> = {}
|
||||
for (const b of branchList.remote) {
|
||||
const slashIndex = b.indexOf("/")
|
||||
const remoteName = slashIndex > 0 ? b.substring(0, slashIndex) : "origin"
|
||||
if (!groups[remoteName]) groups[remoteName] = []
|
||||
groups[remoteName].push(b)
|
||||
}
|
||||
return groups
|
||||
}, [branchList.remote])
|
||||
const remoteNames = Object.keys(groupedRemoteBranches)
|
||||
const hasMultipleRemotes = remoteNames.length > 1
|
||||
|
||||
useEffect(() => {
|
||||
if (!folder) return
|
||||
@@ -611,6 +626,19 @@ export function BranchDropdown({
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuGroup>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuGroup>
|
||||
<DropdownMenuItem
|
||||
disabled={loading}
|
||||
onSelect={() => {
|
||||
setDropdownOpen(false)
|
||||
setManageRemotesOpen(true)
|
||||
}}
|
||||
>
|
||||
<Globe className="h-3.5 w-3.5" />
|
||||
{t("manageRemotes")}
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuGroup>
|
||||
<DropdownMenuSeparator />
|
||||
{branchLoading ? (
|
||||
<div className="flex items-center justify-center py-3">
|
||||
<Loader2 className="h-4 w-4 animate-spin text-muted-foreground" />
|
||||
@@ -643,6 +671,20 @@ export function BranchDropdown({
|
||||
<DropdownMenuItem disabled>
|
||||
{t("noRemoteBranches")}
|
||||
</DropdownMenuItem>
|
||||
) : hasMultipleRemotes ? (
|
||||
remoteNames.map((remoteName) => (
|
||||
<Collapsible key={remoteName}>
|
||||
<CollapsibleTrigger className="flex w-full items-center gap-2.5 rounded-xl px-3 py-2 pl-6 text-sm hover:bg-accent hover:text-accent-foreground select-none outline-hidden">
|
||||
<ChevronRight className="h-3 w-3 shrink-0 transition-transform [[data-state=open]>&]:rotate-90" />
|
||||
{remoteName} ({groupedRemoteBranches[remoteName].length})
|
||||
</CollapsibleTrigger>
|
||||
<CollapsibleContent>
|
||||
{groupedRemoteBranches[remoteName].map((b) =>
|
||||
renderBranchItem(b, true)
|
||||
)}
|
||||
</CollapsibleContent>
|
||||
</Collapsible>
|
||||
))
|
||||
) : (
|
||||
branchList.remote.map((b) => renderBranchItem(b, true))
|
||||
)}
|
||||
@@ -782,6 +824,13 @@ export function BranchDropdown({
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
<RemoteManageDialog
|
||||
open={manageRemotesOpen}
|
||||
onOpenChange={setManageRemotesOpen}
|
||||
folderPath={folderPath}
|
||||
onSaved={() => loadAllBranches()}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
250
src/components/layout/remote-manage-dialog.tsx
Normal file
250
src/components/layout/remote-manage-dialog.tsx
Normal file
@@ -0,0 +1,250 @@
|
||||
"use client"
|
||||
|
||||
import { useState, useEffect } from "react"
|
||||
import { useTranslations } from "next-intl"
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogFooter,
|
||||
} from "@/components/ui/dialog"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import { ScrollArea } from "@/components/ui/scroll-area"
|
||||
import { Plus, Trash2 } from "lucide-react"
|
||||
import {
|
||||
gitListRemotes,
|
||||
gitFetchRemote,
|
||||
gitAddRemote,
|
||||
gitRemoveRemote,
|
||||
gitSetRemoteUrl,
|
||||
} from "@/lib/tauri"
|
||||
|
||||
interface RemoteDraft {
|
||||
originalName: string | null
|
||||
originalUrl: string
|
||||
name: string
|
||||
url: string
|
||||
deleted: boolean
|
||||
}
|
||||
|
||||
interface RemoteManageDialogProps {
|
||||
open: boolean
|
||||
onOpenChange: (open: boolean) => void
|
||||
folderPath: string
|
||||
onSaved: () => void
|
||||
}
|
||||
|
||||
export function RemoteManageDialog({
|
||||
open,
|
||||
onOpenChange,
|
||||
folderPath,
|
||||
onSaved,
|
||||
}: RemoteManageDialogProps) {
|
||||
const t = useTranslations("Folder.branchDropdown.dialogs")
|
||||
const tCommon = useTranslations("Folder.common")
|
||||
const [drafts, setDrafts] = useState<RemoteDraft[]>([])
|
||||
const [saving, setSaving] = useState(false)
|
||||
const [loadingRemotes, setLoadingRemotes] = useState(false)
|
||||
const [errors, setErrors] = useState<Record<number, string>>({})
|
||||
|
||||
useEffect(() => {
|
||||
if (open) {
|
||||
setErrors({})
|
||||
setLoadingRemotes(true)
|
||||
gitListRemotes(folderPath)
|
||||
.then((remotes) => {
|
||||
setDrafts(
|
||||
remotes.map((r) => ({
|
||||
originalName: r.name,
|
||||
originalUrl: r.url,
|
||||
name: r.name,
|
||||
url: r.url,
|
||||
deleted: false,
|
||||
}))
|
||||
)
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error("Failed to load remotes:", err)
|
||||
setDrafts([])
|
||||
})
|
||||
.finally(() => setLoadingRemotes(false))
|
||||
}
|
||||
}, [open, folderPath])
|
||||
|
||||
const addDraft = () => {
|
||||
setDrafts((prev) => [
|
||||
...prev,
|
||||
{ originalName: null, originalUrl: "", name: "", url: "", deleted: false },
|
||||
])
|
||||
}
|
||||
|
||||
const updateDraft = (
|
||||
index: number,
|
||||
field: "name" | "url",
|
||||
value: string
|
||||
) => {
|
||||
setDrafts((prev) =>
|
||||
prev.map((d, i) => (i === index ? { ...d, [field]: value } : d))
|
||||
)
|
||||
}
|
||||
|
||||
const removeDraft = (index: number) => {
|
||||
setDrafts((prev) =>
|
||||
prev.map((d, i) => (i === index ? { ...d, deleted: true } : d))
|
||||
)
|
||||
}
|
||||
|
||||
const extractError = (err: unknown): string => {
|
||||
if (err && typeof err === "object") {
|
||||
const e = err as Record<string, unknown>
|
||||
if (typeof e.detail === "string" && e.detail) return e.detail
|
||||
if (typeof e.message === "string" && e.message) return e.message
|
||||
}
|
||||
return String(err)
|
||||
}
|
||||
|
||||
const handleSave = async () => {
|
||||
setSaving(true)
|
||||
setErrors({})
|
||||
const newErrors: Record<number, string> = {}
|
||||
try {
|
||||
// Process deletions first
|
||||
for (const draft of drafts) {
|
||||
if (draft.deleted && draft.originalName != null) {
|
||||
await gitRemoveRemote(folderPath, draft.originalName)
|
||||
}
|
||||
}
|
||||
// Process additions
|
||||
for (let i = 0; i < drafts.length; i++) {
|
||||
const draft = drafts[i]
|
||||
if (draft.deleted) continue
|
||||
if (draft.originalName == null && draft.name && draft.url) {
|
||||
try {
|
||||
await gitAddRemote(folderPath, draft.name, draft.url)
|
||||
} catch (err) {
|
||||
newErrors[i] = extractError(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
// Process URL modifications
|
||||
for (let i = 0; i < drafts.length; i++) {
|
||||
const draft = drafts[i]
|
||||
if (draft.deleted || draft.originalName == null) continue
|
||||
if (draft.url !== draft.originalUrl) {
|
||||
try {
|
||||
await gitSetRemoteUrl(folderPath, draft.originalName, draft.url)
|
||||
} catch (err) {
|
||||
newErrors[i] = extractError(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
// Fetch all surviving remotes
|
||||
for (let i = 0; i < drafts.length; i++) {
|
||||
const draft = drafts[i]
|
||||
if (draft.deleted || newErrors[i]) continue
|
||||
const remoteName = draft.originalName ?? draft.name
|
||||
if (!remoteName) continue
|
||||
try {
|
||||
await gitFetchRemote(folderPath, remoteName)
|
||||
} catch (err) {
|
||||
newErrors[i] = extractError(err)
|
||||
}
|
||||
}
|
||||
if (Object.keys(newErrors).length > 0) {
|
||||
setErrors(newErrors)
|
||||
} else {
|
||||
onSaved()
|
||||
onOpenChange(false)
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Failed to save remotes:", err)
|
||||
} finally {
|
||||
setSaving(false)
|
||||
}
|
||||
}
|
||||
|
||||
const visibleDrafts = drafts.filter((d) => !d.deleted)
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent className="max-w-lg">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{t("manageRemotesTitle")}</DialogTitle>
|
||||
</DialogHeader>
|
||||
<ScrollArea className="max-h-72">
|
||||
<div className="space-y-2 pr-2">
|
||||
{loadingRemotes ? (
|
||||
<p className="text-sm text-muted-foreground text-center py-4">
|
||||
...
|
||||
</p>
|
||||
) : visibleDrafts.length === 0 ? (
|
||||
<p className="text-sm text-muted-foreground text-center py-4">
|
||||
{t("manageRemotesEmpty")}
|
||||
</p>
|
||||
) : (
|
||||
drafts.map(
|
||||
(draft, index) =>
|
||||
!draft.deleted && (
|
||||
<div key={index} className="space-y-1">
|
||||
<div className="flex items-center gap-2">
|
||||
<Input
|
||||
placeholder={t("remoteNamePlaceholder")}
|
||||
value={draft.name}
|
||||
onChange={(e) =>
|
||||
updateDraft(index, "name", e.target.value)
|
||||
}
|
||||
disabled={draft.originalName != null}
|
||||
className={`h-8 text-sm w-32 shrink-0 ${errors[index] ? "border-destructive" : ""}`}
|
||||
/>
|
||||
<Input
|
||||
placeholder={t("remoteUrlPlaceholder")}
|
||||
value={draft.url}
|
||||
onChange={(e) =>
|
||||
updateDraft(index, "url", e.target.value)
|
||||
}
|
||||
className={`h-8 text-sm font-mono flex-1 ${errors[index] ? "border-destructive" : ""}`}
|
||||
/>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="h-8 w-8 shrink-0"
|
||||
onClick={() => removeDraft(index)}
|
||||
>
|
||||
<Trash2 className="h-3.5 w-3.5 text-destructive" />
|
||||
</Button>
|
||||
</div>
|
||||
{errors[index] && (
|
||||
<p className="text-xs text-destructive pl-1 truncate">
|
||||
{errors[index]}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
</ScrollArea>
|
||||
<DialogFooter className="flex items-center justify-between sm:justify-between">
|
||||
<Button variant="outline" size="sm" onClick={addDraft}>
|
||||
<Plus className="h-3.5 w-3.5 mr-1" />
|
||||
{t("addRemote")}
|
||||
</Button>
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => onOpenChange(false)}
|
||||
>
|
||||
{tCommon("cancel")}
|
||||
</Button>
|
||||
<Button size="sm" onClick={handleSave} disabled={saving}>
|
||||
{saving ? t("savingRemotes") : tCommon("save")}
|
||||
</Button>
|
||||
</div>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
@@ -794,6 +794,7 @@
|
||||
"newWorktree": "Worktree جديد...",
|
||||
"stashChanges": "تخزين التغييرات في stash",
|
||||
"stashPop": "استرجاع stash...",
|
||||
"manageRemotes": "إدارة المستودعات البعيدة...",
|
||||
"localBranches": "الفروع المحلية ({count, plural, one {#} other {#}})",
|
||||
"noLocalBranches": "لا توجد فروع محلية",
|
||||
"remoteBranches": "الفروع البعيدة ({count, plural, one {#} other {#}})",
|
||||
@@ -807,7 +808,13 @@
|
||||
"newWorktreeDescription": "إنشاء worktree جديد من الفرع الحالي {branch}",
|
||||
"branchNameLabel": "اسم الفرع",
|
||||
"worktreePathLabel": "مسار worktree",
|
||||
"worktreePathPlaceholder": "مسار worktree"
|
||||
"worktreePathPlaceholder": "مسار worktree",
|
||||
"manageRemotesTitle": "إدارة المستودعات البعيدة",
|
||||
"manageRemotesEmpty": "لم يتم تكوين أي مستودعات بعيدة",
|
||||
"remoteNamePlaceholder": "اسم المستودع البعيد",
|
||||
"remoteUrlPlaceholder": "عنوان URL للمستودع البعيد",
|
||||
"addRemote": "إضافة",
|
||||
"savingRemotes": "جارٍ الحفظ..."
|
||||
}
|
||||
},
|
||||
"commitDialog": {
|
||||
|
||||
@@ -794,6 +794,7 @@
|
||||
"newWorktree": "Neuer Worktree...",
|
||||
"stashChanges": "Änderungen stashen",
|
||||
"stashPop": "Stash anwenden...",
|
||||
"manageRemotes": "Remotes verwalten...",
|
||||
"localBranches": "Lokale Branches ({count, plural, one {#} other {#}})",
|
||||
"noLocalBranches": "Keine lokalen Branches",
|
||||
"remoteBranches": "Remote-Branches ({count, plural, one {#} other {#}})",
|
||||
@@ -807,7 +808,13 @@
|
||||
"newWorktreeDescription": "Neuen Worktree vom aktuellen Branch {branch} erstellen",
|
||||
"branchNameLabel": "Branch-Name",
|
||||
"worktreePathLabel": "Worktree-Pfad",
|
||||
"worktreePathPlaceholder": "Worktree-Pfad"
|
||||
"worktreePathPlaceholder": "Worktree-Pfad",
|
||||
"manageRemotesTitle": "Remotes verwalten",
|
||||
"manageRemotesEmpty": "Keine Remotes konfiguriert",
|
||||
"remoteNamePlaceholder": "Remote-Name",
|
||||
"remoteUrlPlaceholder": "Remote-URL",
|
||||
"addRemote": "Hinzufügen",
|
||||
"savingRemotes": "Speichern..."
|
||||
}
|
||||
},
|
||||
"commitDialog": {
|
||||
|
||||
@@ -794,6 +794,7 @@
|
||||
"newWorktree": "New worktree...",
|
||||
"stashChanges": "Stash changes",
|
||||
"stashPop": "Pop stash...",
|
||||
"manageRemotes": "Manage Remotes...",
|
||||
"localBranches": "Local branches ({count, plural, one {#} other {#}})",
|
||||
"noLocalBranches": "No local branches",
|
||||
"remoteBranches": "Remote branches ({count, plural, one {#} other {#}})",
|
||||
@@ -807,7 +808,13 @@
|
||||
"newWorktreeDescription": "Create a new worktree from current branch {branch}",
|
||||
"branchNameLabel": "Branch name",
|
||||
"worktreePathLabel": "Worktree path",
|
||||
"worktreePathPlaceholder": "Worktree path"
|
||||
"worktreePathPlaceholder": "Worktree path",
|
||||
"manageRemotesTitle": "Manage Remotes",
|
||||
"manageRemotesEmpty": "No remotes configured",
|
||||
"remoteNamePlaceholder": "Remote name",
|
||||
"remoteUrlPlaceholder": "Remote URL",
|
||||
"addRemote": "Add",
|
||||
"savingRemotes": "Saving..."
|
||||
}
|
||||
},
|
||||
"commitDialog": {
|
||||
|
||||
@@ -794,6 +794,7 @@
|
||||
"newWorktree": "Nuevo worktree...",
|
||||
"stashChanges": "Guardar cambios en stash",
|
||||
"stashPop": "Aplicar stash...",
|
||||
"manageRemotes": "Gestionar remotos...",
|
||||
"localBranches": "Ramas locales ({count, plural, one {#} other {#}})",
|
||||
"noLocalBranches": "Sin ramas locales",
|
||||
"remoteBranches": "Ramas remotas ({count, plural, one {#} other {#}})",
|
||||
@@ -807,7 +808,13 @@
|
||||
"newWorktreeDescription": "Crear un nuevo worktree desde la rama actual {branch}",
|
||||
"branchNameLabel": "Nombre de la rama",
|
||||
"worktreePathLabel": "Ruta del worktree",
|
||||
"worktreePathPlaceholder": "Ruta del worktree"
|
||||
"worktreePathPlaceholder": "Ruta del worktree",
|
||||
"manageRemotesTitle": "Gestionar remotos",
|
||||
"manageRemotesEmpty": "No hay remotos configurados",
|
||||
"remoteNamePlaceholder": "Nombre del remoto",
|
||||
"remoteUrlPlaceholder": "URL del remoto",
|
||||
"addRemote": "Añadir",
|
||||
"savingRemotes": "Guardando..."
|
||||
}
|
||||
},
|
||||
"commitDialog": {
|
||||
|
||||
@@ -794,6 +794,7 @@
|
||||
"newWorktree": "Nouveau worktree...",
|
||||
"stashChanges": "Stash des changements",
|
||||
"stashPop": "Appliquer le stash...",
|
||||
"manageRemotes": "Gérer les dépôts distants...",
|
||||
"localBranches": "Branches locales ({count, plural, one {#} other {#}})",
|
||||
"noLocalBranches": "Aucune branche locale",
|
||||
"remoteBranches": "Branches distantes ({count, plural, one {#} other {#}})",
|
||||
@@ -807,7 +808,13 @@
|
||||
"newWorktreeDescription": "Créer un nouveau worktree depuis la branche actuelle {branch}",
|
||||
"branchNameLabel": "Nom de la branche",
|
||||
"worktreePathLabel": "Chemin du worktree",
|
||||
"worktreePathPlaceholder": "Chemin du worktree"
|
||||
"worktreePathPlaceholder": "Chemin du worktree",
|
||||
"manageRemotesTitle": "Gérer les dépôts distants",
|
||||
"manageRemotesEmpty": "Aucun dépôt distant configuré",
|
||||
"remoteNamePlaceholder": "Nom du dépôt distant",
|
||||
"remoteUrlPlaceholder": "URL du dépôt distant",
|
||||
"addRemote": "Ajouter",
|
||||
"savingRemotes": "Enregistrement..."
|
||||
}
|
||||
},
|
||||
"commitDialog": {
|
||||
|
||||
@@ -794,6 +794,7 @@
|
||||
"newWorktree": "新規ワークツリー...",
|
||||
"stashChanges": "変更を stash",
|
||||
"stashPop": "stash を pop...",
|
||||
"manageRemotes": "リモート管理...",
|
||||
"localBranches": "ローカルブランチ ({count, plural, one {#} other {#}})",
|
||||
"noLocalBranches": "ローカルブランチはありません",
|
||||
"remoteBranches": "リモートブランチ ({count, plural, one {#} other {#}})",
|
||||
@@ -807,7 +808,13 @@
|
||||
"newWorktreeDescription": "現在のブランチ {branch} から新しいワークツリーを作成",
|
||||
"branchNameLabel": "ブランチ名",
|
||||
"worktreePathLabel": "ワークツリーのパス",
|
||||
"worktreePathPlaceholder": "ワークツリーのパス"
|
||||
"worktreePathPlaceholder": "ワークツリーのパス",
|
||||
"manageRemotesTitle": "リモート管理",
|
||||
"manageRemotesEmpty": "リモートが設定されていません",
|
||||
"remoteNamePlaceholder": "リモート名",
|
||||
"remoteUrlPlaceholder": "リモート URL",
|
||||
"addRemote": "追加",
|
||||
"savingRemotes": "保存中..."
|
||||
}
|
||||
},
|
||||
"commitDialog": {
|
||||
|
||||
@@ -794,6 +794,7 @@
|
||||
"newWorktree": "새 워크트리...",
|
||||
"stashChanges": "변경 사항 stash",
|
||||
"stashPop": "stash pop...",
|
||||
"manageRemotes": "원격 관리...",
|
||||
"localBranches": "로컬 브랜치 ({count, plural, one {#} other {#}})",
|
||||
"noLocalBranches": "로컬 브랜치가 없습니다",
|
||||
"remoteBranches": "원격 브랜치 ({count, plural, one {#} other {#}})",
|
||||
@@ -807,7 +808,13 @@
|
||||
"newWorktreeDescription": "현재 브랜치 {branch}에서 새 워크트리를 만듭니다",
|
||||
"branchNameLabel": "브랜치 이름",
|
||||
"worktreePathLabel": "워크트리 경로",
|
||||
"worktreePathPlaceholder": "워크트리 경로"
|
||||
"worktreePathPlaceholder": "워크트리 경로",
|
||||
"manageRemotesTitle": "원격 관리",
|
||||
"manageRemotesEmpty": "구성된 원격이 없습니다",
|
||||
"remoteNamePlaceholder": "원격 이름",
|
||||
"remoteUrlPlaceholder": "원격 URL",
|
||||
"addRemote": "추가",
|
||||
"savingRemotes": "저장 중..."
|
||||
}
|
||||
},
|
||||
"commitDialog": {
|
||||
|
||||
@@ -794,6 +794,7 @@
|
||||
"newWorktree": "Novo worktree...",
|
||||
"stashChanges": "Fazer stash das alterações",
|
||||
"stashPop": "Aplicar stash...",
|
||||
"manageRemotes": "Gerenciar remotos...",
|
||||
"localBranches": "Branches locais ({count, plural, one {#} other {#}})",
|
||||
"noLocalBranches": "Sem branches locais",
|
||||
"remoteBranches": "Branches remotas ({count, plural, one {#} other {#}})",
|
||||
@@ -807,7 +808,13 @@
|
||||
"newWorktreeDescription": "Criar um novo worktree a partir da branch atual {branch}",
|
||||
"branchNameLabel": "Nome da branch",
|
||||
"worktreePathLabel": "Caminho do worktree",
|
||||
"worktreePathPlaceholder": "Caminho do worktree"
|
||||
"worktreePathPlaceholder": "Caminho do worktree",
|
||||
"manageRemotesTitle": "Gerenciar remotos",
|
||||
"manageRemotesEmpty": "Nenhum remoto configurado",
|
||||
"remoteNamePlaceholder": "Nome do remoto",
|
||||
"remoteUrlPlaceholder": "URL do remoto",
|
||||
"addRemote": "Adicionar",
|
||||
"savingRemotes": "Salvando..."
|
||||
}
|
||||
},
|
||||
"commitDialog": {
|
||||
|
||||
@@ -794,6 +794,7 @@
|
||||
"newWorktree": "新建工作树...",
|
||||
"stashChanges": "贮藏更改",
|
||||
"stashPop": "取消贮藏...",
|
||||
"manageRemotes": "管理远程...",
|
||||
"localBranches": "本地分支 ({count})",
|
||||
"noLocalBranches": "无本地分支",
|
||||
"remoteBranches": "远程分支 ({count})",
|
||||
@@ -807,7 +808,13 @@
|
||||
"newWorktreeDescription": "从当前分支 {branch} 创建新的工作树",
|
||||
"branchNameLabel": "分支名称",
|
||||
"worktreePathLabel": "工作树路径",
|
||||
"worktreePathPlaceholder": "工作树路径"
|
||||
"worktreePathPlaceholder": "工作树路径",
|
||||
"manageRemotesTitle": "管理远程",
|
||||
"manageRemotesEmpty": "未配置远程仓库",
|
||||
"remoteNamePlaceholder": "远程名称",
|
||||
"remoteUrlPlaceholder": "远程 URL",
|
||||
"addRemote": "添加",
|
||||
"savingRemotes": "保存中..."
|
||||
}
|
||||
},
|
||||
"commitDialog": {
|
||||
|
||||
@@ -794,6 +794,7 @@
|
||||
"newWorktree": "新增工作樹...",
|
||||
"stashChanges": "暫存變更",
|
||||
"stashPop": "取消暫存...",
|
||||
"manageRemotes": "管理遠端...",
|
||||
"localBranches": "本地分支 ({count})",
|
||||
"noLocalBranches": "無本地分支",
|
||||
"remoteBranches": "遠端分支 ({count})",
|
||||
@@ -807,7 +808,13 @@
|
||||
"newWorktreeDescription": "從目前分支 {branch} 建立新的工作樹",
|
||||
"branchNameLabel": "分支名稱",
|
||||
"worktreePathLabel": "工作樹路徑",
|
||||
"worktreePathPlaceholder": "工作樹路徑"
|
||||
"worktreePathPlaceholder": "工作樹路徑",
|
||||
"manageRemotesTitle": "管理遠端",
|
||||
"manageRemotesEmpty": "未設定遠端儲存庫",
|
||||
"remoteNamePlaceholder": "遠端名稱",
|
||||
"remoteUrlPlaceholder": "遠端 URL",
|
||||
"addRemote": "新增",
|
||||
"savingRemotes": "儲存中..."
|
||||
}
|
||||
},
|
||||
"commitDialog": {
|
||||
|
||||
@@ -25,6 +25,7 @@ import type {
|
||||
GitPushResult,
|
||||
GitMergeResult,
|
||||
GitCommitResult,
|
||||
GitRemote,
|
||||
PreflightResult,
|
||||
FolderCommand,
|
||||
TerminalInfo,
|
||||
@@ -522,6 +523,40 @@ export async function gitStashPop(path: string): Promise<string> {
|
||||
return invoke("git_stash_pop", { path })
|
||||
}
|
||||
|
||||
export async function gitListRemotes(path: string): Promise<GitRemote[]> {
|
||||
return invoke("git_list_remotes", { path })
|
||||
}
|
||||
|
||||
export async function gitFetchRemote(
|
||||
path: string,
|
||||
name: string
|
||||
): Promise<string> {
|
||||
return invoke("git_fetch_remote", { path, name })
|
||||
}
|
||||
|
||||
export async function gitAddRemote(
|
||||
path: string,
|
||||
name: string,
|
||||
url: string
|
||||
): Promise<void> {
|
||||
return invoke("git_add_remote", { path, name, url })
|
||||
}
|
||||
|
||||
export async function gitRemoveRemote(
|
||||
path: string,
|
||||
name: string
|
||||
): Promise<void> {
|
||||
return invoke("git_remove_remote", { path, name })
|
||||
}
|
||||
|
||||
export async function gitSetRemoteUrl(
|
||||
path: string,
|
||||
name: string,
|
||||
url: string
|
||||
): Promise<void> {
|
||||
return invoke("git_set_remote_url", { path, name, url })
|
||||
}
|
||||
|
||||
export async function gitStatus(path: string): Promise<GitStatusEntry[]> {
|
||||
return invoke("git_status", { path })
|
||||
}
|
||||
|
||||
@@ -683,6 +683,11 @@ export interface GitCommitResult {
|
||||
committed_files: number
|
||||
}
|
||||
|
||||
export interface GitRemote {
|
||||
name: string
|
||||
url: string
|
||||
}
|
||||
|
||||
export type FileTreeNode =
|
||||
| { kind: "file"; name: string; path: string }
|
||||
| { kind: "dir"; name: string; path: string; children: FileTreeNode[] }
|
||||
|
||||
Reference in New Issue
Block a user