mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 10:58:30 -05:00
Merge from vscode 718331d6f3ebd1b571530ab499edb266ddd493d5
This commit is contained in:
@@ -752,7 +752,22 @@ export class FileService extends Disposable implements IFileService {
|
||||
// Create directories as needed
|
||||
for (let i = directoriesToCreate.length - 1; i >= 0; i--) {
|
||||
directory = joinPath(directory, directoriesToCreate[i]);
|
||||
await provider.mkdir(directory);
|
||||
|
||||
try {
|
||||
await provider.mkdir(directory);
|
||||
} catch (error) {
|
||||
if (toFileSystemProviderErrorCode(error) !== FileSystemProviderErrorCode.FileExists) {
|
||||
// For mkdirp() we tolerate that the mkdir() call fails
|
||||
// in case the folder already exists. This follows node.js
|
||||
// own implementation of fs.mkdir({ recursive: true }) and
|
||||
// reduces the chances of race conditions leading to errors
|
||||
// if multiple calls try to create the same folders
|
||||
// As such, we only throw an error here if it is other than
|
||||
// the fact that the file already exists.
|
||||
// (see also https://github.com/microsoft/vscode/issues/89834)
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -804,7 +804,7 @@ export interface IFilesConfiguration {
|
||||
eol: string;
|
||||
enableTrash: boolean;
|
||||
hotExit: string;
|
||||
preventSaveConflicts: boolean;
|
||||
saveConflictResolution: 'askUser' | 'overwriteFileOnDisk';
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -76,10 +76,10 @@ export class DiskFileSystemProvider extends Disposable implements
|
||||
|
||||
async stat(resource: URI): Promise<IStat> {
|
||||
try {
|
||||
const { stat, isSymbolicLink } = await statLink(this.toFilePath(resource)); // cannot use fs.stat() here to support links properly
|
||||
const { stat, symbolicLink } = await statLink(this.toFilePath(resource)); // cannot use fs.stat() here to support links properly
|
||||
|
||||
return {
|
||||
type: this.toType(stat, isSymbolicLink),
|
||||
type: this.toType(stat, symbolicLink),
|
||||
ctime: stat.birthtime.getTime(), // intentionally not using ctime here, we want the creation time
|
||||
mtime: stat.mtime.getTime(),
|
||||
size: stat.size
|
||||
@@ -115,12 +115,28 @@ export class DiskFileSystemProvider extends Disposable implements
|
||||
}
|
||||
}
|
||||
|
||||
private toType(entry: Stats | Dirent, isSymbolicLink = entry.isSymbolicLink()): FileType {
|
||||
if (isSymbolicLink) {
|
||||
return FileType.SymbolicLink | (entry.isDirectory() ? FileType.Directory : FileType.File);
|
||||
private toType(entry: Stats | Dirent, symbolicLink?: { dangling: boolean }): FileType {
|
||||
|
||||
// Signal file type by checking for file / directory, except:
|
||||
// - symbolic links pointing to non-existing files are FileType.Unknown
|
||||
// - files that are neither file nor directory are FileType.Unknown
|
||||
let type: FileType;
|
||||
if (symbolicLink?.dangling) {
|
||||
type = FileType.Unknown;
|
||||
} else if (entry.isFile()) {
|
||||
type = FileType.File;
|
||||
} else if (entry.isDirectory()) {
|
||||
type = FileType.Directory;
|
||||
} else {
|
||||
type = FileType.Unknown;
|
||||
}
|
||||
|
||||
return entry.isFile() ? FileType.File : entry.isDirectory() ? FileType.Directory : FileType.Unknown;
|
||||
// Always signal symbolic link as file type additionally
|
||||
if (symbolicLink) {
|
||||
type |= FileType.SymbolicLink;
|
||||
}
|
||||
|
||||
return type;
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
@@ -35,14 +35,14 @@ export class FileWatcher extends Disposable {
|
||||
|
||||
private async startWatching(): Promise<void> {
|
||||
try {
|
||||
const { stat, isSymbolicLink } = await statLink(this.path);
|
||||
const { stat, symbolicLink } = await statLink(this.path);
|
||||
|
||||
if (this.isDisposed) {
|
||||
return;
|
||||
}
|
||||
|
||||
let pathToWatch = this.path;
|
||||
if (isSymbolicLink) {
|
||||
if (symbolicLink) {
|
||||
try {
|
||||
pathToWatch = await realpath(pathToWatch);
|
||||
} catch (error) {
|
||||
|
||||
@@ -8,6 +8,7 @@ import { URI } from 'vs/base/common/uri';
|
||||
import { isEqual, isEqualOrParent } from 'vs/base/common/extpath';
|
||||
import { FileChangeType, FileChangesEvent, isParent } from 'vs/platform/files/common/files';
|
||||
import { isLinux, isMacintosh, isWindows } from 'vs/base/common/platform';
|
||||
// eslint-disable-next-line code-import-patterns
|
||||
import { toResource } from 'vs/base/test/common/utils';
|
||||
|
||||
suite('Files', () => {
|
||||
@@ -19,7 +19,7 @@ import { FileOperation, FileOperationEvent, IFileStat, FileOperationResult, File
|
||||
import { NullLogService } from 'vs/platform/log/common/log';
|
||||
import { isLinux, isWindows } from 'vs/base/common/platform';
|
||||
import { DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { isEqual } from 'vs/base/common/resources';
|
||||
import { isEqual, joinPath } from 'vs/base/common/resources';
|
||||
import { VSBuffer, VSBufferReadable, streamToBufferReadableStream, VSBufferReadableStream, bufferToReadable, bufferToStream, streamToBuffer } from 'vs/base/common/buffer';
|
||||
import { find } from 'vs/base/common/arrays';
|
||||
|
||||
@@ -440,17 +440,22 @@ suite('Disk File Service', function () {
|
||||
assert.equal(resolved.isSymbolicLink, true);
|
||||
});
|
||||
|
||||
test('resolve - invalid symbolic link does not break', async () => {
|
||||
test('resolve - symbolic link pointing to non-existing file does not break', async () => {
|
||||
if (isWindows) {
|
||||
return; // not reliable on windows
|
||||
}
|
||||
|
||||
const link = URI.file(join(testDir, 'foo'));
|
||||
await symlink(link.fsPath, join(testDir, 'bar'));
|
||||
await symlink(join(testDir, 'foo'), join(testDir, 'bar'));
|
||||
|
||||
const resolved = await service.resolve(URI.file(testDir));
|
||||
assert.equal(resolved.isDirectory, true);
|
||||
assert.equal(resolved.children!.length, 8);
|
||||
assert.equal(resolved.children!.length, 9);
|
||||
|
||||
const resolvedLink = resolved.children?.filter(child => child.name === 'bar' && child.isSymbolicLink)[0];
|
||||
assert.ok(resolvedLink);
|
||||
|
||||
assert.ok(!resolvedLink?.isDirectory);
|
||||
assert.ok(!resolvedLink?.isFile);
|
||||
});
|
||||
|
||||
test('deleteFile', async () => {
|
||||
@@ -479,6 +484,52 @@ suite('Disk File Service', function () {
|
||||
assert.equal((<FileOperationError>error).fileOperationResult, FileOperationResult.FILE_NOT_FOUND);
|
||||
});
|
||||
|
||||
test('deleteFile - symbolic link (exists)', async () => {
|
||||
if (isWindows) {
|
||||
return; // not reliable on windows
|
||||
}
|
||||
|
||||
const target = URI.file(join(testDir, 'lorem.txt'));
|
||||
const link = URI.file(join(testDir, 'lorem.txt-linked'));
|
||||
await symlink(target.fsPath, link.fsPath);
|
||||
|
||||
const source = await service.resolve(link);
|
||||
|
||||
let event: FileOperationEvent;
|
||||
disposables.add(service.onAfterOperation(e => event = e));
|
||||
|
||||
await service.del(source.resource);
|
||||
|
||||
assert.equal(existsSync(source.resource.fsPath), false);
|
||||
|
||||
assert.ok(event!);
|
||||
assert.equal(event!.resource.fsPath, link.fsPath);
|
||||
assert.equal(event!.operation, FileOperation.DELETE);
|
||||
|
||||
assert.equal(existsSync(target.fsPath), true); // target the link pointed to is never deleted
|
||||
});
|
||||
|
||||
test('deleteFile - symbolic link (pointing to non-existing file)', async () => {
|
||||
if (isWindows) {
|
||||
return; // not reliable on windows
|
||||
}
|
||||
|
||||
const target = URI.file(join(testDir, 'foo'));
|
||||
const link = URI.file(join(testDir, 'bar'));
|
||||
await symlink(target.fsPath, link.fsPath);
|
||||
|
||||
let event: FileOperationEvent;
|
||||
disposables.add(service.onAfterOperation(e => event = e));
|
||||
|
||||
await service.del(link);
|
||||
|
||||
assert.equal(existsSync(link.fsPath), false);
|
||||
|
||||
assert.ok(event!);
|
||||
assert.equal(event!.resource.fsPath, link.fsPath);
|
||||
assert.equal(event!.operation, FileOperation.DELETE);
|
||||
});
|
||||
|
||||
test('deleteFolder (recursive)', async () => {
|
||||
let event: FileOperationEvent;
|
||||
disposables.add(service.onAfterOperation(e => event = e));
|
||||
@@ -1867,6 +1918,47 @@ suite('Disk File Service', function () {
|
||||
assert.ok(!error);
|
||||
});
|
||||
|
||||
test('writeFile - no error when writing to same non-existing folder multiple times different new files', async () => {
|
||||
const newFolder = URI.file(join(testDir, 'some', 'new', 'folder'));
|
||||
|
||||
const file1 = joinPath(newFolder, 'file-1');
|
||||
const file2 = joinPath(newFolder, 'file-2');
|
||||
const file3 = joinPath(newFolder, 'file-3');
|
||||
|
||||
// this essentially verifies that the mkdirp logic implemented
|
||||
// in the file service is able to receive multiple requests for
|
||||
// the same folder and will not throw errors if another racing
|
||||
// call succeeded first.
|
||||
const newContent = 'Updates to the small file';
|
||||
await Promise.all([
|
||||
service.writeFile(file1, VSBuffer.fromString(newContent)),
|
||||
service.writeFile(file2, VSBuffer.fromString(newContent)),
|
||||
service.writeFile(file3, VSBuffer.fromString(newContent))
|
||||
]);
|
||||
|
||||
assert.ok(service.exists(file1));
|
||||
assert.ok(service.exists(file2));
|
||||
assert.ok(service.exists(file3));
|
||||
});
|
||||
|
||||
test('writeFile - error when writing to folder that is a file', async () => {
|
||||
const existingFile = URI.file(join(testDir, 'my-file'));
|
||||
|
||||
await service.createFile(existingFile);
|
||||
|
||||
const newFile = joinPath(existingFile, 'file-1');
|
||||
|
||||
let error;
|
||||
const newContent = 'Updates to the small file';
|
||||
try {
|
||||
await service.writeFile(newFile, VSBuffer.fromString(newContent));
|
||||
} catch (e) {
|
||||
error = e;
|
||||
}
|
||||
|
||||
assert.ok(error);
|
||||
});
|
||||
|
||||
const runWatchTests = isLinux;
|
||||
|
||||
(runWatchTests ? test : test.skip)('watch - file', done => {
|
||||
|
||||
Reference in New Issue
Block a user