Git - adopt the new package to use copy-on-write for the worktree include files (#299583)

* Git - adopt the new package to use copy-on-write for the worktree include files

---------

Co-authored-by: deepak1556 <hop2deep@gmail.com>
This commit is contained in:
Ladislau Szomoru
2026-03-10 11:51:57 +01:00
committed by GitHub
parent 11246017b6
commit 950ab0704b
3 changed files with 68 additions and 29 deletions

View File

@@ -11,6 +11,7 @@
"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",
@@ -218,6 +219,19 @@
"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",
@@ -274,6 +288,12 @@
"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",

View File

@@ -4346,6 +4346,7 @@
"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",

View File

@@ -3,6 +3,7 @@
* 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';
@@ -1930,59 +1931,76 @@ export class Repository implements Disposable {
gitIgnoredFiles.delete(uri.fsPath);
}
// Add the folder paths for git ignored files
// 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<string>();
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.
const gitIgnoredPaths = new Set(gitIgnoredFiles);
for (const filePath of gitIgnoredFiles) {
let dir = path.dirname(filePath);
while (dir !== this.root && !gitIgnoredFiles.has(dir)) {
while (dir !== this.root && !gitIgnoredPaths.has(dir)) {
gitIgnoredPaths.add(dir);
if (filePatternBases.has(dir)) {
break;
}
dir = path.dirname(dir);
}
}
return gitIgnoredPaths;
// 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<string>();
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;
}
private async _copyWorktreeIncludeFiles(worktreePath: string): Promise<void> {
const gitIgnoredPaths = await this._getWorktreeIncludePaths();
if (gitIgnoredPaths.size === 0) {
const worktreeIncludePaths = await this._getWorktreeIncludePaths();
if (worktreeIncludePaths.size === 0) {
return;
}
try {
// 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<string>();
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 startTime = performance.now();
const limiter = new Limiter<void>(15);
const files = Array.from(pathsToCopy);
const files = Array.from(worktreeIncludePaths);
// Copy files
const results = await Promise.allSettled(files.map(sourceFile =>
limiter.queue(async () => {
const results = await Promise.allSettled(files.map(sourceFile => {
return limiter.queue(async () => {
const targetFile = path.join(worktreePath, relativePath(this.root, sourceFile));
await fsPromises.mkdir(path.dirname(targetFile), { recursive: true });
await fsPromises.cp(sourceFile, targetFile, {
filter: src => gitIgnoredPaths.has(src),
force: true,
mode: fs.constants.COPYFILE_FICLONE,
recursive: true,
verbatimSymlinks: true
});
})
));
await cp(sourceFile, targetFile, { force: true, 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. [${Date.now() - startTime}ms]`);
this.logger.info(`[Repository][_copyWorktreeIncludeFiles] Copied ${files.length - failedOperations.length}/${files.length} folder(s)/file(s) to worktree. [${(performance.now() - startTime).toFixed(2)}ms]`);
if (failedOperations.length > 0) {
window.showWarningMessage(l10n.t('Failed to copy {0} folder(s)/file(s) to the worktree.', failedOperations.length));