支持在git推送时选择远程源
This commit is contained in:
@@ -3,9 +3,11 @@
|
||||
import type { ReactElement } from "react"
|
||||
import { useCallback, useEffect, useMemo, useState } from "react"
|
||||
import {
|
||||
ArrowRight,
|
||||
ChevronsDownUp,
|
||||
ChevronsUpDown,
|
||||
CloudOff,
|
||||
GitBranch,
|
||||
Loader2,
|
||||
Upload,
|
||||
} from "lucide-react"
|
||||
@@ -42,10 +44,17 @@ import {
|
||||
} from "@/components/ai-elements/commit"
|
||||
import { DiffViewer } from "@/components/diff/diff-viewer"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { gitLog, gitPush, gitShowFile } from "@/lib/tauri"
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select"
|
||||
import { gitLog, gitPush, gitPushInfo, gitShowFile } from "@/lib/tauri"
|
||||
import { toErrorMessage } from "@/lib/app-error"
|
||||
import { languageFromPath } from "@/lib/language-detect"
|
||||
import type { GitLogEntry, GitLogFileChange } from "@/lib/types"
|
||||
import type { GitLogEntry, GitLogFileChange, GitPushInfo } from "@/lib/types"
|
||||
import { useGitCredential } from "@/contexts/git-credential-context"
|
||||
|
||||
// --- File tree types & builder (same as aux-panel-git-log-tab) ---
|
||||
@@ -278,6 +287,8 @@ export function PushWorkspace({
|
||||
const tLog = useTranslations("Folder.gitLogTab")
|
||||
const { withCredentialRetry } = useGitCredential()
|
||||
|
||||
const [pushInfoData, setPushInfoData] = useState<GitPushInfo | null>(null)
|
||||
const [selectedRemote, setSelectedRemote] = useState<string | null>(null)
|
||||
const [commits, setCommits] = useState<GitLogEntry[]>([])
|
||||
const [hasUpstream, setHasUpstream] = useState(true)
|
||||
const [listLoading, setListLoading] = useState(false)
|
||||
@@ -295,22 +306,60 @@ export function PushWorkspace({
|
||||
[commits]
|
||||
)
|
||||
|
||||
const loadCommits = useCallback(async () => {
|
||||
setListLoading(true)
|
||||
try {
|
||||
const result = await gitLog(folderPath, 100)
|
||||
setCommits(result.entries)
|
||||
setHasUpstream(result.has_upstream)
|
||||
} catch (err) {
|
||||
toast.error(toErrorMessage(err))
|
||||
} finally {
|
||||
setListLoading(false)
|
||||
}
|
||||
// Load push info (branch, remotes, tracking remote)
|
||||
useEffect(() => {
|
||||
gitPushInfo(folderPath)
|
||||
.then((info) => {
|
||||
setPushInfoData(info)
|
||||
// Default to tracking remote or first remote
|
||||
const defaultRemote =
|
||||
info.tracking_remote ??
|
||||
(info.remotes.length > 0 ? info.remotes[0].name : null)
|
||||
setSelectedRemote(defaultRemote)
|
||||
})
|
||||
.catch((err) => {
|
||||
toast.error(toErrorMessage(err))
|
||||
})
|
||||
}, [folderPath])
|
||||
|
||||
// Deduplicate remotes (git remote -v returns fetch + push entries)
|
||||
const uniqueRemotes = useMemo(() => {
|
||||
if (!pushInfoData) return []
|
||||
const seen = new Set<string>()
|
||||
return pushInfoData.remotes.filter((r) => {
|
||||
if (seen.has(r.name)) return false
|
||||
seen.add(r.name)
|
||||
return true
|
||||
})
|
||||
}, [pushInfoData])
|
||||
|
||||
const loadCommits = useCallback(
|
||||
async (remote?: string) => {
|
||||
setListLoading(true)
|
||||
try {
|
||||
const result = await gitLog(
|
||||
folderPath,
|
||||
100,
|
||||
undefined,
|
||||
remote ?? undefined
|
||||
)
|
||||
setCommits(result.entries)
|
||||
setHasUpstream(result.has_upstream)
|
||||
} catch (err) {
|
||||
toast.error(toErrorMessage(err))
|
||||
} finally {
|
||||
setListLoading(false)
|
||||
}
|
||||
},
|
||||
[folderPath]
|
||||
)
|
||||
|
||||
// Reload commits when selected remote changes
|
||||
useEffect(() => {
|
||||
loadCommits()
|
||||
}, [loadCommits])
|
||||
if (selectedRemote !== null) {
|
||||
loadCommits(selectedRemote)
|
||||
}
|
||||
}, [selectedRemote, loadCommits])
|
||||
|
||||
async function handleSelectFile(commitHash: string, file: string) {
|
||||
setSelectedFile(file)
|
||||
@@ -334,9 +383,10 @@ export function PushWorkspace({
|
||||
async function handlePush() {
|
||||
setPushing(true)
|
||||
try {
|
||||
await withCredentialRetry((creds) => gitPush(folderPath, creds), {
|
||||
folderPath,
|
||||
})
|
||||
await withCredentialRetry(
|
||||
(creds) => gitPush(folderPath, selectedRemote, creds),
|
||||
{ folderPath }
|
||||
)
|
||||
onPushed?.()
|
||||
} catch (err) {
|
||||
toast.error(t("toasts.pushFailed"), {
|
||||
@@ -349,6 +399,43 @@ export function PushWorkspace({
|
||||
|
||||
return (
|
||||
<div className="flex h-full flex-col">
|
||||
{/* Push target header: branch → remote/branch */}
|
||||
{pushInfoData && (
|
||||
<div className="flex items-center gap-2 border-b px-3 py-2">
|
||||
<GitBranch className="h-4 w-4 shrink-0 text-muted-foreground" />
|
||||
<span className="truncate text-sm font-medium">
|
||||
{pushInfoData.branch}
|
||||
</span>
|
||||
<ArrowRight className="h-3.5 w-3.5 shrink-0 text-muted-foreground" />
|
||||
{uniqueRemotes.length <= 1 ? (
|
||||
<span className="truncate text-sm text-muted-foreground">
|
||||
{selectedRemote ?? "origin"}/{pushInfoData.branch}
|
||||
</span>
|
||||
) : (
|
||||
<div className="flex items-center gap-1">
|
||||
<Select
|
||||
value={selectedRemote ?? ""}
|
||||
onValueChange={setSelectedRemote}
|
||||
>
|
||||
<SelectTrigger className="h-7 w-auto gap-1 border-none bg-transparent px-1.5 text-sm shadow-none">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{uniqueRemotes.map((r) => (
|
||||
<SelectItem key={r.name} value={r.name}>
|
||||
{r.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<span className="text-sm text-muted-foreground">
|
||||
/{pushInfoData.branch}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<ResizablePanelGroup direction="horizontal" className="min-h-0 flex-1">
|
||||
{/* Left panel: commit list */}
|
||||
<ResizablePanel defaultSize={35} minSize={25}>
|
||||
|
||||
Reference in New Issue
Block a user