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 <noreply@anthropic.com>
This commit is contained in:
Ruben Fiszel
2026-01-28 19:09:03 +00:00
parent 9be12bb607
commit eafee16bfc
+11 -4
View File
@@ -1283,6 +1283,8 @@ export async function elementsToMap(
): Promise<{ [key: string]: string }> {
const map: { [key: string]: string } = {};
const processedBasePaths = new Set<string>();
// 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(