Skip to content

Commit 37953c4

Browse files
Tyriareli-w-king
authored andcommitted
Merge branch 'main' into copilot/sub-pr-286838
2 parents ee3f79e + b77b7eb commit 37953c4

File tree

22 files changed

+356
-178
lines changed

22 files changed

+356
-178
lines changed

extensions/git/package.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3570,6 +3570,17 @@
35703570
"default": 50,
35713571
"description": "%config.detectWorktreesLimit%"
35723572
},
3573+
"git.worktreeIncludeFiles": {
3574+
"type": "array",
3575+
"items": {
3576+
"type": "string"
3577+
},
3578+
"default": [
3579+
"**/node_modules{,/**}"
3580+
],
3581+
"scope": "resource",
3582+
"markdownDescription": "%config.worktreeIncludeFiles%"
3583+
},
35733584
"git.alwaysShowStagedChangesResourceGroup": {
35743585
"type": "boolean",
35753586
"scope": "resource",

extensions/git/package.nls.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,7 @@
236236
"config.detectSubmodulesLimit": "Controls the limit of Git submodules detected.",
237237
"config.detectWorktrees": "Controls whether to automatically detect Git worktrees.",
238238
"config.detectWorktreesLimit": "Controls the limit of Git worktrees detected.",
239+
"config.worktreeIncludeFiles": "Configure [glob patterns](https://aka.ms/vscode-glob-patterns) for files and folders that are included when creating a new worktree. Only files and folders that match the patterns and are listed in `.gitignore` will be copied to the newly created worktree.",
239240
"config.alwaysShowStagedChangesResourceGroup": "Always show the Staged Changes resource group.",
240241
"config.alwaysSignOff": "Controls the signoff flag for all commits.",
241242
"config.ignoreSubmodules": "Ignore modifications to submodules in the file tree.",

extensions/git/src/repository.ts

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
import TelemetryReporter from '@vscode/extension-telemetry';
77
import * as fs from 'fs';
8+
import * as fsPromises from 'fs/promises';
89
import * as path from 'path';
910
import picomatch from 'picomatch';
1011
import { CancellationError, CancellationToken, CancellationTokenSource, Command, commands, Disposable, Event, EventEmitter, FileDecoration, FileType, l10n, LogLevel, LogOutputChannel, Memento, ProgressLocation, ProgressOptions, QuickDiffProvider, RelativePattern, scm, SourceControl, SourceControlInputBox, SourceControlInputBoxValidation, SourceControlInputBoxValidationType, SourceControlResourceDecorations, SourceControlResourceGroup, SourceControlResourceState, TabInputNotebookDiff, TabInputTextDiff, TabInputTextMultiDiff, ThemeColor, ThemeIcon, Uri, window, workspace, WorkspaceEdit } from 'vscode';
@@ -1878,10 +1879,132 @@ export class Repository implements Disposable {
18781879
this.globalState.update(`${Repository.WORKTREE_ROOT_STORAGE_KEY}:${this.root}`, newWorktreeRoot);
18791880
}
18801881

1882+
// Copy worktree include files. We explicitly do not await this
1883+
// since we don't want to block the worktree creation on the
1884+
// copy operation.
1885+
this._copyWorktreeIncludeFiles(worktreePath!);
1886+
18811887
return worktreePath!;
18821888
});
18831889
}
18841890

1891+
private async _getWorktreeIncludeFiles(): Promise<Set<string>> {
1892+
const config = workspace.getConfiguration('git', Uri.file(this.root));
1893+
const worktreeIncludeFiles = config.get<string[]>('worktreeIncludeFiles', ['**/node_modules{,/**}']);
1894+
1895+
if (worktreeIncludeFiles.length === 0) {
1896+
return new Set<string>();
1897+
}
1898+
1899+
try {
1900+
// Expand the glob patterns
1901+
const matchedFiles = new Set<string>();
1902+
for (const pattern of worktreeIncludeFiles) {
1903+
for await (const file of fsPromises.glob(pattern, { cwd: this.root })) {
1904+
matchedFiles.add(file);
1905+
}
1906+
}
1907+
1908+
// Collect unique directories from all the matched files. Check
1909+
// first whether directories are ignored in order to limit the
1910+
// number of git check-ignore calls.
1911+
const directoriesToCheck = new Set<string>();
1912+
for (const file of matchedFiles) {
1913+
let parent = path.dirname(file);
1914+
while (parent && parent !== '.') {
1915+
directoriesToCheck.add(path.join(this.root, parent));
1916+
parent = path.dirname(parent);
1917+
}
1918+
}
1919+
1920+
const gitIgnoredDirectories = await this.checkIgnore(Array.from(directoriesToCheck));
1921+
1922+
// Files under a git ignored directory are ignored
1923+
const gitIgnoredFiles = new Set<string>();
1924+
const filesToCheck: string[] = [];
1925+
1926+
for (const file of matchedFiles) {
1927+
const fullPath = path.join(this.root, file);
1928+
let parent = path.dirname(fullPath);
1929+
let isUnderIgnoredDir = false;
1930+
1931+
while (parent !== this.root && parent.length > this.root.length) {
1932+
if (gitIgnoredDirectories.has(parent)) {
1933+
isUnderIgnoredDir = true;
1934+
break;
1935+
}
1936+
parent = path.dirname(parent);
1937+
}
1938+
1939+
if (isUnderIgnoredDir) {
1940+
gitIgnoredFiles.add(fullPath);
1941+
} else {
1942+
filesToCheck.push(fullPath);
1943+
}
1944+
}
1945+
1946+
// Check the files that are not under a git ignored directories
1947+
const filesToCheckResults = await this.checkIgnore(Array.from(filesToCheck));
1948+
filesToCheckResults.forEach(ignoredFile => gitIgnoredFiles.add(ignoredFile));
1949+
1950+
return gitIgnoredFiles;
1951+
} catch (err) {
1952+
this.logger.warn(`[Repository][_getWorktreeIncludeFiles] Failed to get worktree include files: ${err}`);
1953+
return new Set<string>();
1954+
}
1955+
}
1956+
1957+
private async _copyWorktreeIncludeFiles(worktreePath: string): Promise<void> {
1958+
const ignoredFiles = await this._getWorktreeIncludeFiles();
1959+
if (ignoredFiles.size === 0) {
1960+
return;
1961+
}
1962+
1963+
try {
1964+
// Copy files
1965+
let copiedFiles = 0;
1966+
const results = await window.withProgress({
1967+
location: ProgressLocation.Notification,
1968+
title: l10n.t('Copying additional files to the worktree'),
1969+
cancellable: false
1970+
}, async (progress) => {
1971+
const limiter = new Limiter<void>(10);
1972+
const files = Array.from(ignoredFiles);
1973+
1974+
return Promise.allSettled(files.map(sourceFile =>
1975+
limiter.queue(async () => {
1976+
const targetFile = path.join(worktreePath, relativePath(this.root, sourceFile));
1977+
await fsPromises.mkdir(path.dirname(targetFile), { recursive: true });
1978+
await fsPromises.cp(sourceFile, targetFile, {
1979+
force: true,
1980+
recursive: false,
1981+
verbatimSymlinks: true
1982+
});
1983+
1984+
copiedFiles++;
1985+
progress.report({
1986+
increment: 100 / ignoredFiles.size,
1987+
message: l10n.t('({0}/{1})', copiedFiles, ignoredFiles.size)
1988+
});
1989+
})
1990+
));
1991+
});
1992+
1993+
// When expanding the glob patterns, both directories and files are matched however
1994+
// directories cannot be copied so we filter out `ERR_FS_EISDIR` errors as those are
1995+
// expected.
1996+
const errors = results.filter(r => r.status === 'rejected' &&
1997+
(r.reason as NodeJS.ErrnoException).code !== 'ERR_FS_EISDIR');
1998+
1999+
if (errors.length > 0) {
2000+
this.logger.warn(`[Repository][_copyWorktreeIncludeFiles] Failed to copy ${errors.length} files to worktree.`);
2001+
window.showWarningMessage(l10n.t('Failed to copy {0} files to the worktree.', errors.length));
2002+
}
2003+
} catch (err) {
2004+
this.logger.warn(`[Repository][_copyWorktreeIncludeFiles] Failed to copy files to worktree: ${err}`);
2005+
}
2006+
}
2007+
18852008
async deleteWorktree(path: string, options?: { force?: boolean }): Promise<void> {
18862009
await this.run(Operation.Worktree(false), async () => {
18872010
const worktree = this.repositoryResolver.getRepository(path);

extensions/package-lock.json

Lines changed: 10 additions & 68 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

extensions/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
"postinstall": "node ./postinstall.mjs"
1111
},
1212
"devDependencies": {
13-
"@parcel/watcher": "parcel-bundler/watcher#f503c6eb8df1e883f6989f11743232e43ccb90f6",
13+
"@parcel/watcher": "parcel-bundler/watcher#7a9a5d6af0ce2a4d9b892cab3abb3a464373b105",
1414
"esbuild": "0.27.2",
1515
"vscode-grammar-updater": "^1.1.0"
1616
},

0 commit comments

Comments
 (0)