import { open } from "@tauri-apps/plugin-dialog"; import { gitClone } from "@yaakapp-internal/git"; import { useState } from "react"; import { openWorkspaceFromSyncDir } from "../commands/openWorkspaceFromSyncDir"; import { appInfo } from "../lib/appInfo"; import { showErrorToast } from "../lib/toast"; import { Banner } from "./core/Banner"; import { Button } from "./core/Button"; import { Checkbox } from "./core/Checkbox"; import { IconButton } from "./core/IconButton"; import { PlainInput } from "./core/PlainInput"; import { VStack } from "./core/Stacks"; import { promptCredentials } from "./git/credentials"; interface Props { hide: () => void; } // Detect path separator from an existing path (defaults to /) function getPathSeparator(path: string): string { return path.includes("\\") ? "\\" : "/"; } export function CloneGitRepositoryDialog({ hide }: Props) { const [url, setUrl] = useState(""); const [baseDirectory, setBaseDirectory] = useState(appInfo.defaultProjectDir); const [directoryOverride, setDirectoryOverride] = useState(null); const [hasSubdirectory, setHasSubdirectory] = useState(false); const [subdirectory, setSubdirectory] = useState(""); const [isCloning, setIsCloning] = useState(false); const [error, setError] = useState(null); const repoName = extractRepoName(url); const sep = getPathSeparator(baseDirectory); const computedDirectory = repoName ? `${baseDirectory}${sep}${repoName}` : baseDirectory; const directory = directoryOverride ?? computedDirectory; const workspaceDirectory = hasSubdirectory && subdirectory ? `${directory}${sep}${subdirectory}` : directory; const handleSelectDirectory = async () => { const dir = await open({ title: "Select Directory", directory: true, multiple: false, }); if (dir != null) { setBaseDirectory(dir); setDirectoryOverride(null); } }; const handleClone = async (e: React.FormEvent) => { e.preventDefault(); if (!url || !directory) return; setIsCloning(true); setError(null); try { const result = await gitClone(url, directory, promptCredentials); if (result.type === "needs_credentials") { setError( result.error ?? "Authentication failed. Please check your credentials and try again.", ); return; } // Open the workspace from the cloned directory (or subdirectory) await openWorkspaceFromSyncDir.mutateAsync(workspaceDirectory); hide(); } catch (err) { setError(String(err)); showErrorToast({ id: "git-clone-error", title: "Clone Failed", message: String(err), }); } finally { setIsCloning(false); } }; return ( {error && ( {error} )} } /> {hasSubdirectory && ( )} ); } function extractRepoName(url: string): string { // Handle various Git URL formats: // https://github.com/user/repo.git // git@github.com:user/repo.git // https://github.com/user/repo const match = url.match(/\/([^/]+?)(\.git)?$/); if (match?.[1]) { return match[1]; } // Fallback for SSH-style URLs const sshMatch = url.match(/:([^/]+?)(\.git)?$/); if (sshMatch?.[1]) { return sshMatch[1]; } return ""; }