feat(git): add advanced clone options (shallow, sparse, flags)

Add advanced clone options support across the frontend and backend.
Users can specify a branch, shallow limits, blobless mode, sparse paths,
and custom clone flags when initiating a clone operation. The backend
validates inputs, parses custom flags without invoking a shell, and
configures sparse checkouts after a successful clone. A small parsing
dependency was added and tracked-file parsing was improved to better
handle git ls-files output.

- Introduce CloneRunOptions and validation helpers for clone inputs
- Parse custom flags with a shell-like tokenizer and block managed flags
- Support sparse checkout configuration and shallow clone constraints
This commit is contained in:
2026-08-31 19:43:30 +02:00
parent 4c58b14691
commit 90df347e4a
7 changed files with 455 additions and 15 deletions
+10 -5
View File
@@ -148,6 +148,7 @@
AppLanguage,
AppTheme,
AnalyticsSettings,
CloneOptions,
CustomThemeColors,
ConflictFile,
DetectedExternalTool,
@@ -233,6 +234,7 @@
remoteUrl: string;
parentPath: string;
directoryName: string;
cloneOptions?: CloneOptions;
}
interface ErrorAutoHideState {
@@ -719,7 +721,7 @@
if (pendingStartupCloneRequest) {
const request = pendingStartupCloneRequest;
pendingStartupCloneRequest = null;
await cloneRepo(request.remoteUrl, request.parentPath, request.directoryName);
await cloneRepo(request.remoteUrl, request.parentPath, request.directoryName, undefined, undefined, undefined, false, "credentials", request.cloneOptions);
return;
}
const path = pendingStartupRepoPath;
@@ -2462,12 +2464,13 @@
key?: string | null,
fromStore = false,
credentialMode: CredentialMode = "credentials",
cloneOptions: CloneOptions = { branch: null, blobless: false, customFlags: "", shallowDepth: null, shallowSince: null, sparse: false, sparsePaths: [] },
) {
if (isBusy) return;
if (!remoteUrl) { errorMessage = "Enter a remote URL."; return; }
if (!parentPath) { errorMessage = "Select a destination folder."; return; }
const request: CloneRequest = { remoteUrl, parentPath, directoryName };
const request: CloneRequest = { remoteUrl, parentPath, directoryName, cloneOptions };
pendingClone = request;
const credentialKey = key === undefined ? orgKeyFromUrl(remoteUrl) : key;
@@ -2483,7 +2486,7 @@
credDialogOpen = true;
return;
}
await cloneRepo(remoteUrl, parentPath, directoryName, stored.username, stored.password, credentialKey, true, storedMode);
await cloneRepo(remoteUrl, parentPath, directoryName, stored.username, stored.password, credentialKey, true, storedMode, cloneOptions);
return;
}
}
@@ -2499,6 +2502,7 @@
username,
password,
COMMIT_HISTORY_PAGE_SIZE + 1,
cloneOptions,
);
resetRepositoryState(false);
await applyRepositoryBundle(bundle.status.repo_path, bundle);
@@ -2555,9 +2559,9 @@
trackEvent("clone_dialog_opened");
}
function cloneFromDialog(remoteUrl: string, parentPath: string, directoryName: string, provider?: GitIntegrationProvider, accountId?: string) {
function cloneFromDialog(remoteUrl: string, parentPath: string, directoryName: string, cloneOptions: CloneOptions, provider?: GitIntegrationProvider, accountId?: string) {
const credentialKey = provider ? integrationCredentialKey(provider, accountId) : undefined;
void cloneRepo(remoteUrl, parentPath, directoryName, undefined, undefined, credentialKey, false, provider ? "token" : "credentials");
void cloneRepo(remoteUrl, parentPath, directoryName, undefined, undefined, credentialKey, false, provider ? "token" : "credentials", cloneOptions);
}
function openRepoManagement() {
@@ -3877,6 +3881,7 @@
key,
false,
mode,
pendingClone.cloneOptions,
);
}
}