feat(clone): support credentialed git clone with askpass

Cloning now accepts optional username/password and passes them to the
Rust git layer via GIT_ASKPASS, avoiding interactive prompts. The UI
detects authentication failures, opens the credential dialog for clone,
and auto-hides error messages to keep the flow smooth.

- Add username/password support to clone_repository and git.ts
- Detect auth failures and route users to the clone credential dialog
- Improve clone UX with a dedicated loading overlay and timed errors
This commit is contained in:
2026-07-05 01:18:26 +02:00
parent 32497d53df
commit b646a2c647
7 changed files with 247 additions and 25 deletions
+16 -1
View File
@@ -1,5 +1,5 @@
<script lang="ts">
import { onMount } from "svelte";
import { onDestroy, onMount } from "svelte";
import { AlertCircle, Bot, Check, Cpu, Eye, EyeOff, Gauge, Globe, Key, LoaderCircle, Sparkles, X, Zap } from "@lucide/svelte";
import { credDelete, credLoad, credSave } from "../git";
import type { AiSettings, CommitAiLocalProfile, CommitAiProvider, LocalModelOption } from "../types";
@@ -36,6 +36,7 @@
let loadingKeys = $state(true);
let saving = $state(false);
let error = $state("");
let errorHideTimer: ReturnType<typeof setTimeout> | undefined;
$effect(() => {
provider = settings.provider;
@@ -47,6 +48,16 @@
customModel = settings.customModel;
});
$effect(() => {
if (errorHideTimer) clearTimeout(errorHideTimer);
const currentError = error;
if (currentError) {
errorHideTimer = setTimeout(() => {
if (error === currentError) error = "";
}, 6000);
}
});
onMount(() => {
(async () => {
try {
@@ -66,6 +77,10 @@
})();
});
onDestroy(() => {
if (errorHideTimer) clearTimeout(errorHideTimer);
});
async function persistKey(target: CloudProvider, value: string) {
const key = CRED_KEYS[target];
const trimmed = value.trim();
@@ -1,4 +1,5 @@
<script lang="ts">
import { onDestroy } from "svelte";
import { open as openDialog } from "@tauri-apps/plugin-dialog";
import { Download, FolderOpen, LoaderCircle, X } from "@lucide/svelte";
@@ -22,6 +23,8 @@
let directoryNameEdited = $state(false);
let directoryAutoName = $state("");
let browseError = $state("");
let visibleError = $state("");
let errorHideTimer: ReturnType<typeof setTimeout> | undefined;
let directorySuggestion = $derived(directoryNameFromRemoteUrl(remoteUrl));
let canSubmit = $derived(
@@ -29,7 +32,21 @@
remoteUrl.trim().length > 0 &&
parentPath.trim().length > 0,
);
let visibleError = $derived(error || browseError);
$effect(() => {
const nextError = error || browseError;
if (errorHideTimer) clearTimeout(errorHideTimer);
visibleError = nextError;
if (nextError) {
errorHideTimer = setTimeout(() => {
visibleError = "";
}, 6000);
}
});
onDestroy(() => {
if (errorHideTimer) clearTimeout(errorHideTimer);
});
function directoryNameFromRemoteUrl(url: string): string {
const trimmed = url.trim().split(/[?#]/, 1)[0]?.replace(/[\\/]+$/g, "") ?? "";
+12 -4
View File
@@ -14,7 +14,7 @@
} from "@lucide/svelte";
interface Props {
action: "push" | "pull" | "fetch";
action: "push" | "pull" | "fetch" | "clone";
error: string;
isBusy: boolean;
onSubmit: (username: string, password: string, save: boolean, expiresAt: string | null) => void;
@@ -43,13 +43,21 @@
password.trim().length > 0 &&
(mode === "token" || username.trim().length > 0),
);
let actionLabel = $derived(action === "push" ? "Push" : action === "fetch" ? "Fetch" : "Pull");
let actionLabel = $derived(action === "push" ? "Push" : action === "fetch" ? "Fetch" : action === "clone" ? "Clone" : "Pull");
let actionTitle = $derived(
action === "push" ? "Authenticate push" : action === "fetch" ? "Authenticate fetch" : "Authenticate pull",
action === "push"
? "Authenticate push"
: action === "fetch"
? "Authenticate fetch"
: action === "clone"
? "Authenticate clone"
: "Authenticate pull",
);
let actionHint = $derived(action === "push"
? "The remote needs write access. Use a password or a token with the appropriate repository permissions."
: "The remote needs access to the repository. Use your Git credentials or a personal access token.");
: action === "clone"
? "The repository needs access before it can be cloned. Use your Git credentials or a personal access token."
: "The remote needs access to the repository. Use your Git credentials or a personal access token.");
function handleSubmit(e: SubmitEvent) {
e.preventDefault();