* Cache a marketplace from a git repository * * Clones or updates a git repository containing marketplace data. * If the repository already exists at cachePath, pulls the latest changes. * If pulling fails, removes the directory and re-clones. * * Example repository structure: * ``` * my-mar
(
gitUrl: string,
cachePath: string,
ref?: string,
sparsePaths?: string[],
onProgress?: MarketplaceProgressCallback,
options?: { disableCredentialHelper?: boolean },
)
| 1082 | * @param onProgress - Optional callback to report progress |
| 1083 | */ |
| 1084 | async function cacheMarketplaceFromGit( |
| 1085 | gitUrl: string, |
| 1086 | cachePath: string, |
| 1087 | ref?: string, |
| 1088 | sparsePaths?: string[], |
| 1089 | onProgress?: MarketplaceProgressCallback, |
| 1090 | options?: { disableCredentialHelper?: boolean }, |
| 1091 | ): Promise<void> { |
| 1092 | const fs = getFsImplementation() |
| 1093 | |
| 1094 | // Attempt incremental update; fall back to re-clone if the repo is absent, |
| 1095 | // stale, or otherwise not updatable. Using pull-first avoids a stat-before-operate |
| 1096 | // TOCTOU check: gitPull returns non-zero when cachePath is missing or has no .git. |
| 1097 | const timeoutSec = Math.round(getPluginGitTimeoutMs() / 1000) |
| 1098 | safeCallProgress( |
| 1099 | onProgress, |
| 1100 | `Refreshing marketplace cache (timeout: ${timeoutSec}s)…`, |
| 1101 | ) |
| 1102 | |
| 1103 | // Reconcile sparse-checkout config before pulling. If this requires a re-clone |
| 1104 | // (Sparse→Full transition) or fails (missing dir, not a repo), skip straight |
| 1105 | // to the rm+clone fallback. |
| 1106 | const reconcileResult = await reconcileSparseCheckout(cachePath, sparsePaths) |
| 1107 | if (reconcileResult.code === 0) { |
| 1108 | const pullStarted = performance.now() |
| 1109 | const pullResult = await gitPull(cachePath, ref, { |
| 1110 | disableCredentialHelper: options?.disableCredentialHelper, |
| 1111 | sparsePaths, |
| 1112 | }) |
| 1113 | logPluginFetch( |
| 1114 | 'marketplace_pull', |
| 1115 | gitUrl, |
| 1116 | pullResult.code === 0 ? 'success' : 'failure', |
| 1117 | performance.now() - pullStarted, |
| 1118 | pullResult.code === 0 ? undefined : classifyFetchError(pullResult.stderr), |
| 1119 | ) |
| 1120 | if (pullResult.code === 0) return |
| 1121 | logForDebugging(`git pull failed, will re-clone: ${pullResult.stderr}`, { |
| 1122 | level: 'warn', |
| 1123 | }) |
| 1124 | } else { |
| 1125 | logForDebugging( |
| 1126 | `sparse-checkout reconcile requires re-clone: ${reconcileResult.stderr}`, |
| 1127 | ) |
| 1128 | } |
| 1129 | |
| 1130 | try { |
| 1131 | await fs.rm(cachePath, { recursive: true }) |
| 1132 | // rm succeeded — a stale or partially-cloned directory existed; log for diagnostics |
| 1133 | logForDebugging( |
| 1134 | `Found stale marketplace directory at ${cachePath}, cleaning up to allow re-clone`, |
| 1135 | { level: 'warn' }, |
| 1136 | ) |
| 1137 | safeCallProgress( |
| 1138 | onProgress, |
| 1139 | 'Found stale directory, cleaning up and re-cloning…', |
| 1140 | ) |
| 1141 | } catch (rmError) { |
no test coverage detected