diff --git a/services/web/app/src/Features/GitSync/GitSyncHandler.mjs b/services/web/app/src/Features/GitSync/GitSyncHandler.mjs index bc20a55edd..bc7e70beb5 100644 --- a/services/web/app/src/Features/GitSync/GitSyncHandler.mjs +++ b/services/web/app/src/Features/GitSync/GitSyncHandler.mjs @@ -169,13 +169,25 @@ async function pushToRemote( // objects are sent — git handles the diff automatically. const repoDir = await ensureRepo(projectId, remoteUrl, branch) - // Fetch remote state (trees only, no blobs) to preserve files outside the managed area. - // read-tree updates the index without checking out any files, so no blobs are downloaded. + // Fetch remote state (trees only, latest commit only) to preserve files outside the managed area. + // Skipped when remote HEAD already matches local HEAD (common case: re-push without external changes). try { - await spawnGit(['fetch', '--filter=blob:none', 'origin', branch], repoDir) - // --mixed: moves HEAD to FETCH_HEAD + updates index, no working tree change. - // This makes FETCH_HEAD the parent of our next commit so the diff only shows subPath changes. - await spawnGit(['reset', '--mixed', 'FETCH_HEAD'], repoDir) + let needsFetch = true + try { + const remoteRef = await spawnGitOutput(['ls-remote', 'origin', branch], repoDir) + const remoteHead = remoteRef.split(/\s/)[0].trim() + const localHead = await spawnGitOutput(['rev-parse', 'HEAD'], repoDir) + needsFetch = remoteHead !== localHead + } catch { + // Can't compare — proceed with fetch + } + if (needsFetch) { + // --depth=1 --filter=blob:none: only the latest commit's tree objects, no blobs + await spawnGit(['fetch', '--filter=blob:none', '--depth=1', 'origin', branch], repoDir) + // --mixed: moves HEAD to FETCH_HEAD + updates index, no working tree change. + // This makes FETCH_HEAD the parent of our next commit so the diff only shows subPath changes. + await spawnGit(['reset', '--mixed', 'FETCH_HEAD'], repoDir) + } } catch { // Empty remote or first push — proceed with empty index } @@ -256,8 +268,8 @@ async function pullFromRemote(projectId, remoteUrl, subPath, branch, userId) { logger.debug({ projectId, subPath }, 'git sync: fetching remote for pull') const repoDir = await ensureRepo(projectId, remoteUrl, branch) - // Fetch with blob filter — only trees/commits downloaded upfront - await spawnGit(['fetch', '--filter=blob:none', 'origin', branch], repoDir) + // depth=1 + blob filter: only the latest commit's tree objects, no blobs upfront + await spawnGit(['fetch', '--filter=blob:none', '--depth=1', 'origin', branch], repoDir) if (subPath) { // Checkout only the subPath directory — triggers lazy blob fetch for those files only