diff --git a/extensions/git/package-lock.json b/extensions/git/package-lock.json index 6353cbc2753..b552ce9fa5b 100644 --- a/extensions/git/package-lock.json +++ b/extensions/git/package-lock.json @@ -11,7 +11,6 @@ "dependencies": { "@joaomoreno/unique-names-generator": "^5.2.0", "@vscode/extension-telemetry": "^0.9.8", - "@vscode/fs-copyfile": "2.0.0", "byline": "^5.0.0", "file-type": "16.5.4", "picomatch": "2.3.1", @@ -219,19 +218,6 @@ "vscode": "^1.75.0" } }, - "node_modules/@vscode/fs-copyfile": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@vscode/fs-copyfile/-/fs-copyfile-2.0.0.tgz", - "integrity": "sha512-ARb4+9rN905WjJtQ2mSBG/q4pjJkSRun/MkfCeRkk7h/5J8w4vd18NCePFJ/ZucIwXx/7mr9T6nz9Vtt1tk7hg==", - "hasInstallScript": true, - "license": "MIT", - "dependencies": { - "node-addon-api": "^7.0.0" - }, - "engines": { - "node": ">=22.6.0" - } - }, "node_modules/byline": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/byline/-/byline-5.0.0.tgz", @@ -288,12 +274,6 @@ "node": ">=16" } }, - "node_modules/node-addon-api": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", - "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", - "license": "MIT" - }, "node_modules/peek-readable": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/peek-readable/-/peek-readable-4.1.0.tgz", diff --git a/extensions/git/package.json b/extensions/git/package.json index f0e49309944..1fbac49569f 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -4346,7 +4346,6 @@ "dependencies": { "@joaomoreno/unique-names-generator": "^5.2.0", "@vscode/extension-telemetry": "^0.9.8", - "@vscode/fs-copyfile": "2.0.0", "byline": "^5.0.0", "file-type": "16.5.4", "picomatch": "2.3.1", diff --git a/extensions/git/src/repository.ts b/extensions/git/src/repository.ts index 53db3e48495..bd6b6a5c7ff 100644 --- a/extensions/git/src/repository.ts +++ b/extensions/git/src/repository.ts @@ -3,7 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { cp } from '@vscode/fs-copyfile'; import TelemetryReporter from '@vscode/extension-telemetry'; import { uniqueNamesGenerator, adjectives, animals, colors, NumberDictionary } from '@joaomoreno/unique-names-generator'; import * as fs from 'fs'; @@ -1931,76 +1930,59 @@ export class Repository implements Disposable { gitIgnoredFiles.delete(uri.fsPath); } - // Compute the base directory for each glob pattern (the fixed - // prefix before any wildcard characters). This will be used to - // optimize the upward traversal when adding parent directories. - const filePatternBases = new Set(); - for (const pattern of worktreeIncludeFiles) { - const segments = pattern.split(/[\/\\]/); - const fixedSegments: string[] = []; - for (const seg of segments) { - if (/[*?{}[\]]/.test(seg)) { - break; - } - fixedSegments.push(seg); - } - filePatternBases.add(path.join(this.root, ...fixedSegments)); - } - - // Add the folder paths for git ignored files, walking - // up only to the nearest file pattern base directory. + // Add the folder paths for git ignored files const gitIgnoredPaths = new Set(gitIgnoredFiles); for (const filePath of gitIgnoredFiles) { let dir = path.dirname(filePath); - while (dir !== this.root && !gitIgnoredPaths.has(dir)) { + while (dir !== this.root && !gitIgnoredFiles.has(dir)) { gitIgnoredPaths.add(dir); - if (filePatternBases.has(dir)) { - break; - } dir = path.dirname(dir); } } - // Find minimal set of paths (folders and files) to copy. Keep only topmost - // paths — if a directory is already in the set, all its descendants are - // implicitly included and don't need separate entries. - let lastTopmost: string | undefined; - const pathsToCopy = new Set(); - for (const p of Array.from(gitIgnoredPaths).sort()) { - if (lastTopmost && (p === lastTopmost || p.startsWith(lastTopmost + path.sep))) { - continue; - } - pathsToCopy.add(p); - lastTopmost = p; - } - - return pathsToCopy; + return gitIgnoredPaths; } private async _copyWorktreeIncludeFiles(worktreePath: string): Promise { - const worktreeIncludePaths = await this._getWorktreeIncludePaths(); - if (worktreeIncludePaths.size === 0) { + const gitIgnoredPaths = await this._getWorktreeIncludePaths(); + if (gitIgnoredPaths.size === 0) { return; } try { - const startTime = performance.now(); + // Find minimal set of paths (folders and files) to copy. + // The goal is to reduce the number of copy operations + // needed. + const pathsToCopy = new Set(); + for (const filePath of gitIgnoredPaths) { + const relativePath = path.relative(this.root, filePath); + const firstSegment = relativePath.split(path.sep)[0]; + pathsToCopy.add(path.join(this.root, firstSegment)); + } + + const startTime = Date.now(); const limiter = new Limiter(15); - const files = Array.from(worktreeIncludePaths); + const files = Array.from(pathsToCopy); // Copy files - const results = await Promise.allSettled(files.map(sourceFile => { - return limiter.queue(async () => { + const results = await Promise.allSettled(files.map(sourceFile => + limiter.queue(async () => { const targetFile = path.join(worktreePath, relativePath(this.root, sourceFile)); await fsPromises.mkdir(path.dirname(targetFile), { recursive: true }); - await cp(sourceFile, targetFile, { force: true, recursive: true, verbatimSymlinks: true }); - }); - })); + await fsPromises.cp(sourceFile, targetFile, { + filter: src => gitIgnoredPaths.has(src), + force: true, + mode: fs.constants.COPYFILE_FICLONE, + recursive: true, + verbatimSymlinks: true + }); + }) + )); // Log any failed operations const failedOperations = results.filter(r => r.status === 'rejected'); - this.logger.info(`[Repository][_copyWorktreeIncludeFiles] Copied ${files.length - failedOperations.length}/${files.length} folder(s)/file(s) to worktree. [${(performance.now() - startTime).toFixed(2)}ms]`); + this.logger.info(`[Repository][_copyWorktreeIncludeFiles] Copied ${files.length - failedOperations.length}/${files.length} folder(s)/file(s) to worktree. [${Date.now() - startTime}ms]`); if (failedOperations.length > 0) { window.showWarningMessage(l10n.t('Failed to copy {0} folder(s)/file(s) to the worktree.', failedOperations.length));