From eafee16bfc66081a0d1d575020fc4e40c76feb8a Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Wed, 28 Jan 2026 19:09:03 +0000 Subject: [PATCH] fix: cache git branch detection to avoid repeated execSync calls Previously, getCurrentGitBranch() was called inside loops for every file processed during sync pull/push operations. For workspaces with 1900+ files, this spawned thousands of git subprocesses, causing a ~2x performance regression. This fix caches the git branch at the start of: - elementsToMap() for pull operations - push() for push operations Expected improvement: ~3.2s -> ~1.6s for large workspaces. Co-Authored-By: Claude Opus 4.5 --- cli/src/commands/sync/sync.ts | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/cli/src/commands/sync/sync.ts b/cli/src/commands/sync/sync.ts index 3b60eb9d72..55543e7b4d 100644 --- a/cli/src/commands/sync/sync.ts +++ b/cli/src/commands/sync/sync.ts @@ -1283,6 +1283,8 @@ export async function elementsToMap( ): Promise<{ [key: string]: string }> { const map: { [key: string]: string } = {}; const processedBasePaths = new Set(); + // Cache git branch at the start to avoid repeated execSync calls per file + const cachedBranch = branchOverride ?? getCurrentGitBranch() ?? undefined; for await (const entry of readDirRecursiveWithIgnore(ignore, els)) { // console.log("FOO", entry.path, entry.ignored, entry.isDirectory) if (entry.isDirectory || entry.ignored) { @@ -1383,7 +1385,7 @@ export async function elementsToMap( // Handle branch-specific files - skip files for other branches if (specificItems && isBranchSpecificFile(path)) { - if (!isCurrentBranchFile(path, branchOverride)) { + if (!isCurrentBranchFile(path, cachedBranch)) { // Skip branch-specific files for other branches continue; } @@ -1418,9 +1420,9 @@ export async function elementsToMap( } // Handle branch-specific path mapping after all filtering - if (isCurrentBranchFile(path, branchOverride)) { + if (cachedBranch && isCurrentBranchFile(path, cachedBranch)) { // This is a branch-specific file for current branch - const currentBranch = branchOverride || getCurrentGitBranch()!; + const currentBranch = cachedBranch; const basePath = fromBranchSpecificPath(path, currentBranch); // Only use branch-specific files if the item type IS configured as branch-specific @@ -1952,6 +1954,7 @@ export async function pull( } catch { // ignore } + const zipFile = await downloadZip( workspace, opts.plainSecrets, @@ -1976,9 +1979,11 @@ export async function pull( resourceTypeToFormatExtension, true, ); + const local = !opts.stateful ? await FSFSElement(Deno.cwd(), codebases, true) : await FSFSElement(path.join(Deno.cwd(), ".wmill"), [], true); + const changes = await compareDynFSElement( remote, local, @@ -2675,6 +2680,8 @@ export async function push( // Create a pool of workers that processes items as they become available const pool = new Set(); const queue = [...groupedChangesArray]; + // Cache git branch at the start to avoid repeated execSync calls per change + const cachedBranchForPush = opts.branch || (isGitRepository() ? getCurrentGitBranch() : null); while (queue.length > 0 || pool.size > 0) { // Fill the pool until we reach parallelizationFactor @@ -2759,7 +2766,7 @@ export async function push( // For branch-specific resources, push to the base path on the workspace server // This ensures branch-specific files are stored with their base names in the workspace let serverPath = resourceFilePath; - const currentBranch = opts.branch || (isGitRepository() ? getCurrentGitBranch() : null); + const currentBranch = cachedBranchForPush; if (currentBranch && isBranchSpecificFile(resourceFilePath)) { serverPath = fromBranchSpecificPath(