diff --git a/src/vs/workbench/electron-browser/main.ts b/src/vs/workbench/electron-browser/main.ts index 25c39ec5414f943f5bee88aed487a003dfbe9c3a..9afaac5feff9653763882abd67e056541488b02c 100644 --- a/src/vs/workbench/electron-browser/main.ts +++ b/src/vs/workbench/electron-browser/main.ts @@ -13,7 +13,7 @@ import * as comparer from 'vs/base/common/comparers'; import * as platform from 'vs/base/common/platform'; import { URI as uri } from 'vs/base/common/uri'; import { IWorkspaceContextService, Workspace, WorkbenchState } from 'vs/platform/workspace/common/workspace'; -import { WorkspaceService, ISingleFolderWorkspaceInitializationPayload, IMultiFolderWorkspaceInitializationPayload, IEmptyWorkspaceInitializationPayload } from 'vs/workbench/services/configuration/node/configurationService'; +import { WorkspaceService, ISingleFolderWorkspaceInitializationPayload, IMultiFolderWorkspaceInitializationPayload, IEmptyWorkspaceInitializationPayload, IWorkspaceInitializationPayload } from 'vs/workbench/services/configuration/node/configurationService'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { stat } from 'vs/base/node/pfs'; @@ -45,6 +45,7 @@ import { IMenubarService } from 'vs/platform/menubar/common/menubar'; import { Schemas } from 'vs/base/common/network'; import { sanitizeFilePath } from 'vs/base/node/extfs'; import { basename } from 'path'; +import { createHash } from 'crypto'; gracefulFs.gracefulify(fs); // enable gracefulFs @@ -140,7 +141,7 @@ function openWorkbench(configuration: IWindowConfiguration): Promise { } function createAndInitializeWorkspaceService(configuration: IWindowConfiguration, environmentService: EnvironmentService): Promise { - let workspaceInitializationPayload: Promise = Promise.resolve(void 0); + let workspaceInitializationPayload: Promise = Promise.resolve(void 0); // Multi-root workspace if (configuration.workspace) { @@ -154,7 +155,7 @@ function createAndInitializeWorkspaceService(configuration: IWindowConfiguration return workspaceInitializationPayload.then(payload => { - // Fallback to empty workspace + // Fallback to empty workspace if we have no payload yet if (!payload) { payload = { id: configuration.backupPath ? uri.from({ path: basename(configuration.backupPath), scheme: 'empty' }).toString() : '' } as IEmptyWorkspaceInitializationPayload; } @@ -167,17 +168,39 @@ function createAndInitializeWorkspaceService(configuration: IWindowConfiguration function resolveSingleFolderWorkspaceInitializationPayload(folderUri: ISingleFolderWorkspaceIdentifier, verbose: boolean): Promise { + function singleFolderId(folder: uri, stat?: fs.Stats): string { + if (folder.scheme === Schemas.file && stat) { + let ctime: number; + if (platform.isLinux) { + ctime = stat.ino; // Linux: birthtime is ctime, so we cannot use it! We use the ino instead! + } else if (platform.isMacintosh) { + ctime = stat.birthtime.getTime(); // macOS: birthtime is fine to use as is + } else if (platform.isWindows) { + if (typeof stat.birthtimeMs === 'number') { + ctime = Math.floor(stat.birthtimeMs); // Windows: fix precision issue in node.js 8.x to get 7.x results (see https://github.com/nodejs/node/issues/19897) + } else { + ctime = stat.birthtime.getTime(); + } + } + + return createHash('md5').update(folder.fsPath).update(ctime ? String(ctime) : '').digest('hex'); + } + + return createHash('md5').update(folder.toString()).digest('hex'); + } + // Return early the folder is not local if (folderUri.scheme !== Schemas.file) { - return Promise.resolve({ folder: folderUri }); + return Promise.resolve({ id: singleFolderId(folderUri), folder: folderUri }); } // For local: ensure path is absolute and exists const sanitizedFolderPath = sanitizeFilePath(folderUri.fsPath, process.env['VSCODE_CWD'] || process.cwd()); return stat(sanitizedFolderPath).then(stat => { + const sanitizedFolderUri = uri.file(sanitizedFolderPath); return { - folder: uri.file(sanitizedFolderPath), - stat + id: singleFolderId(sanitizedFolderUri, stat), + folder: sanitizedFolderUri } as ISingleFolderWorkspaceInitializationPayload; }, error => { if (verbose) { diff --git a/src/vs/workbench/services/configuration/node/configurationService.ts b/src/vs/workbench/services/configuration/node/configurationService.ts index bf202e9bd298ca7981d8434940271073c39e8ac0..6bf1f5acd8bbe59125b8db4cc285633c01adf9a3 100644 --- a/src/vs/workbench/services/configuration/node/configurationService.ts +++ b/src/vs/workbench/services/configuration/node/configurationService.ts @@ -5,7 +5,6 @@ import { URI } from 'vs/base/common/uri'; import { dirname } from 'path'; -import { Stats } from 'fs'; import * as assert from 'vs/base/common/assert'; import { Event, Emitter } from 'vs/base/common/event'; import { ResourceMap } from 'vs/base/common/map'; @@ -15,7 +14,7 @@ import { Queue } from 'vs/base/common/async'; import { writeFile } from 'vs/base/node/pfs'; import { IJSONContributionRegistry, Extensions as JSONExtensions } from 'vs/platform/jsonschemas/common/jsonContributionRegistry'; import { IWorkspaceContextService, Workspace, WorkbenchState, IWorkspaceFolder, toWorkspaceFolders, IWorkspaceFoldersChangeEvent, WorkspaceFolder } from 'vs/platform/workspace/common/workspace'; -import { isLinux, isWindows, isMacintosh } from 'vs/base/common/platform'; +import { isLinux } from 'vs/base/common/platform'; import { IFileService } from 'vs/platform/files/common/files'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { ConfigurationChangeEvent, ConfigurationModel, DefaultConfigurationModel } from 'vs/platform/configuration/common/configurationModels'; @@ -24,7 +23,6 @@ import { Configuration, WorkspaceConfigurationChangeEvent, AllKeysConfigurationC import { IWorkspaceConfigurationService, FOLDER_CONFIG_FOLDER_NAME, defaultSettingsSchemaId, userSettingsSchemaId, workspaceSettingsSchemaId, folderSettingsSchemaId } from 'vs/workbench/services/configuration/common/configuration'; import { Registry } from 'vs/platform/registry/common/platform'; import { IConfigurationNode, IConfigurationRegistry, Extensions, IConfigurationPropertySchema, allSettings, windowSettings, resourceSettings, applicationSettings } from 'vs/platform/configuration/common/configurationRegistry'; -import { createHash } from 'crypto'; import { IWorkspaceIdentifier, isWorkspaceIdentifier, IStoredWorkspaceFolder, isStoredWorkspaceFolder, IWorkspaceFolderCreationData, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { ICommandService } from 'vs/platform/commands/common/commands'; @@ -41,8 +39,9 @@ import { localize } from 'vs/nls'; import { isEqual } from 'vs/base/common/resources'; export type IMultiFolderWorkspaceInitializationPayload = IWorkspaceIdentifier; -export interface ISingleFolderWorkspaceInitializationPayload { folder: ISingleFolderWorkspaceIdentifier; stat?: Stats; } +export interface ISingleFolderWorkspaceInitializationPayload { id: string; folder: URI; } export interface IEmptyWorkspaceInitializationPayload { id: string; } +export type IWorkspaceInitializationPayload = IMultiFolderWorkspaceInitializationPayload | ISingleFolderWorkspaceInitializationPayload | IEmptyWorkspaceInitializationPayload; function isSingleFolderWorkspaceInitializationPayload(obj: any): obj is ISingleFolderWorkspaceInitializationPayload { return isSingleFolderWorkspaceIdentifier((obj.folder as ISingleFolderWorkspaceIdentifier)); @@ -352,27 +351,15 @@ export class WorkspaceService extends Disposable implements IWorkspaceConfigurat private createSingleFolderWorkspace(singleFolder: ISingleFolderWorkspaceInitializationPayload): Promise { const folder = singleFolder.folder; - if (folder.scheme === Schemas.file && singleFolder.stat) { - const workspaceStat = singleFolder.stat; - let ctime: number; - if (isLinux) { - ctime = workspaceStat.ino; // Linux: birthtime is ctime, so we cannot use it! We use the ino instead! - } else if (isMacintosh) { - ctime = workspaceStat.birthtime.getTime(); // macOS: birthtime is fine to use as is - } else if (isWindows) { - if (typeof workspaceStat.birthtimeMs === 'number') { - ctime = Math.floor(workspaceStat.birthtimeMs); // Windows: fix precision issue in node.js 8.x to get 7.x results (see https://github.com/nodejs/node/issues/19897) - } else { - ctime = workspaceStat.birthtime.getTime(); - } - } - const id = createHash('md5').update(folder.fsPath).update(ctime ? String(ctime) : '').digest('hex'); - return Promise.resolve(new Workspace(id, toWorkspaceFolders([{ path: folder.fsPath }]), null, ctime)); + let configuredFolders: IStoredWorkspaceFolder[]; + if (folder.scheme === 'file') { + configuredFolders = [{ path: folder.fsPath }]; } else { - const id = createHash('md5').update(folder.toString()).digest('hex'); - return Promise.resolve(new Workspace(id, toWorkspaceFolders([{ uri: folder.toString() }]), null)); + configuredFolders = [{ uri: folder.toString() }]; } + + return Promise.resolve(new Workspace(singleFolder.id, toWorkspaceFolders(configuredFolders), null)); } private createEmptyWorkspace(emptyWorkspace: IEmptyWorkspaceInitializationPayload): Promise { diff --git a/src/vs/workbench/services/configuration/test/electron-browser/configurationEditingService.test.ts b/src/vs/workbench/services/configuration/test/electron-browser/configurationEditingService.test.ts index 854138ab682d887739ee83c8b825e300f7bd2224..629b122882d6a524cf105c47008e05cc5186ac03 100644 --- a/src/vs/workbench/services/configuration/test/electron-browser/configurationEditingService.test.ts +++ b/src/vs/workbench/services/configuration/test/electron-browser/configurationEditingService.test.ts @@ -36,6 +36,7 @@ import { ICommandService } from 'vs/platform/commands/common/commands'; import { CommandService } from 'vs/workbench/services/commands/common/commandService'; import { URI } from 'vs/base/common/uri'; import { TPromise } from 'vs/base/common/winjs.base'; +import { createHash } from 'crypto'; class SettingsTestEnvironmentService extends EnvironmentService { @@ -101,7 +102,7 @@ suite('ConfigurationEditingService', () => { instantiationService.stub(IEnvironmentService, environmentService); const workspaceService = new WorkspaceService(environmentService); instantiationService.stub(IWorkspaceContextService, workspaceService); - return workspaceService.initialize(noWorkspace ? { id: '' } : { folder: URI.file(workspaceDir) }).then(() => { + return workspaceService.initialize(noWorkspace ? { id: '' } : { folder: URI.file(workspaceDir), id: createHash('md5').update(URI.file(workspaceDir).toString()).digest('hex') }).then(() => { instantiationService.stub(IConfigurationService, workspaceService); instantiationService.stub(IFileService, new FileService(workspaceService, TestEnvironmentService, new TestTextResourceConfigurationService(), new TestConfigurationService(), new TestLifecycleService(), new TestStorageService(), new TestNotificationService(), { disableWatcher: true })); instantiationService.stub(ITextFileService, instantiationService.createInstance(TestTextFileService)); diff --git a/src/vs/workbench/services/configuration/test/electron-browser/configurationService.test.ts b/src/vs/workbench/services/configuration/test/electron-browser/configurationService.test.ts index 356ad3cda0e11b5a5cb68f9cd7255ffafdc677b1..0b7f22065a59d101d3b742e962131a66120c3e86 100644 --- a/src/vs/workbench/services/configuration/test/electron-browser/configurationService.test.ts +++ b/src/vs/workbench/services/configuration/test/electron-browser/configurationService.test.ts @@ -16,7 +16,7 @@ import { parseArgs } from 'vs/platform/environment/node/argv'; import * as pfs from 'vs/base/node/pfs'; import * as uuid from 'vs/base/common/uuid'; import { IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; -import { WorkspaceService } from 'vs/workbench/services/configuration/node/configurationService'; +import { WorkspaceService, ISingleFolderWorkspaceInitializationPayload } from 'vs/workbench/services/configuration/node/configurationService'; import { ConfigurationEditingErrorCode } from 'vs/workbench/services/configuration/node/configurationEditingService'; import { IFileService } from 'vs/platform/files/common/files'; import { IWorkspaceContextService, WorkbenchState, IWorkspaceFoldersChangeEvent } from 'vs/platform/workspace/common/workspace'; @@ -31,6 +31,8 @@ import { TextModelResolverService } from 'vs/workbench/services/textmodelResolve import { IJSONEditingService } from 'vs/workbench/services/configuration/common/jsonEditing'; import { JSONEditingService } from 'vs/workbench/services/configuration/node/jsonEditingService'; import { IWorkspaceConfigurationService } from 'vs/workbench/services/configuration/common/configuration'; +import { Uri } from 'vscode'; +import { createHash } from 'crypto'; class SettingsTestEnvironmentService extends EnvironmentService { @@ -53,6 +55,13 @@ function setUpFolder(folderName: string, parentDir: string): Promise { return Promise.resolve(pfs.mkdirp(workspaceSettingsDir, 493).then(() => folderDir)); } +function convertToWorkspacePayload(folder: Uri): ISingleFolderWorkspaceInitializationPayload { + return { + id: createHash('md5').update(folder.fsPath).digest('hex'), + folder + } as ISingleFolderWorkspaceInitializationPayload; +} + function setUpWorkspace(folders: string[]): Promise<{ parentDir: string, configPath: string }> { const id = uuid.generateUuid(); @@ -83,7 +92,7 @@ suite('WorkspaceContextService - Folder', () => { const globalSettingsFile = path.join(parentDir, 'settings.json'); const environmentService = new SettingsTestEnvironmentService(parseArgs(process.argv), process.execPath, globalSettingsFile); workspaceContextService = new WorkspaceService(environmentService); - return (workspaceContextService).initialize({ folder: URI.file(folderDir) }); + return (workspaceContextService).initialize(convertToWorkspacePayload(URI.file(folderDir))); }); }); @@ -441,7 +450,7 @@ suite('WorkspaceService - Initialization', () => { testObject.onDidChangeWorkspaceFolders(target); testObject.onDidChangeConfiguration(target); - return testObject.initialize({ folder: URI.file(path.join(parentResource, '1')) }) + return testObject.initialize(convertToWorkspacePayload(URI.file(path.join(parentResource, '1')))) .then(() => { assert.equal(testObject.getValue('initialization.testSetting1'), 'userValue'); assert.equal(target.callCount, 3); @@ -470,7 +479,7 @@ suite('WorkspaceService - Initialization', () => { fs.writeFileSync(path.join(parentResource, '1', '.vscode', 'settings.json'), '{ "initialization.testSetting1": "workspaceValue" }'); - return testObject.initialize({ folder: URI.file(path.join(parentResource, '1')) }) + return testObject.initialize(convertToWorkspacePayload(URI.file(path.join(parentResource, '1')))) .then(() => { assert.equal(testObject.getValue('initialization.testSetting1'), 'workspaceValue'); assert.equal(target.callCount, 4); @@ -544,7 +553,7 @@ suite('WorkspaceService - Initialization', () => { test('initialize a folder workspace from a folder workspace with no configuration changes', () => { - return testObject.initialize({ folder: URI.file(path.join(parentResource, '1')) }) + return testObject.initialize(convertToWorkspacePayload(URI.file(path.join(parentResource, '1')))) .then(() => { fs.writeFileSync(globalSettingsFile, '{ "initialization.testSetting1": "userValue" }'); @@ -556,7 +565,7 @@ suite('WorkspaceService - Initialization', () => { testObject.onDidChangeWorkspaceFolders(target); testObject.onDidChangeConfiguration(target); - return testObject.initialize({ folder: URI.file(path.join(parentResource, '2')) }) + return testObject.initialize(convertToWorkspacePayload(URI.file(path.join(parentResource, '2')))) .then(() => { assert.equal(testObject.getValue('initialization.testSetting1'), 'userValue'); assert.equal(target.callCount, 1); @@ -572,7 +581,7 @@ suite('WorkspaceService - Initialization', () => { test('initialize a folder workspace from a folder workspace with configuration changes', () => { - return testObject.initialize({ folder: URI.file(path.join(parentResource, '1')) }) + return testObject.initialize(convertToWorkspacePayload(URI.file(path.join(parentResource, '1')))) .then(() => { const target = sinon.spy(); @@ -582,7 +591,7 @@ suite('WorkspaceService - Initialization', () => { testObject.onDidChangeConfiguration(target); fs.writeFileSync(path.join(parentResource, '2', '.vscode', 'settings.json'), '{ "initialization.testSetting1": "workspaceValue2" }'); - return testObject.initialize({ folder: URI.file(path.join(parentResource, '2')) }) + return testObject.initialize(convertToWorkspacePayload(URI.file(path.join(parentResource, '2')))) .then(() => { assert.equal(testObject.getValue('initialization.testSetting1'), 'workspaceValue2'); assert.equal(target.callCount, 2); @@ -597,7 +606,7 @@ suite('WorkspaceService - Initialization', () => { test('initialize a multi folder workspace from a folder workspacce triggers change events in the right order', () => { const folderDir = path.join(parentResource, '1'); - return testObject.initialize({ folder: URI.file(folderDir) }) + return testObject.initialize(convertToWorkspacePayload(URI.file(folderDir))) .then(() => { const target = sinon.spy(); @@ -662,7 +671,7 @@ suite('WorkspaceConfigurationService - Folder', () => { instantiationService.stub(IConfigurationService, workspaceService); instantiationService.stub(IEnvironmentService, environmentService); - return workspaceService.initialize({ folder: URI.file(folderDir) }).then(() => { + return workspaceService.initialize(convertToWorkspacePayload(URI.file(folderDir))).then(() => { const fileService = new FileService(workspaceService, TestEnvironmentService, new TestTextResourceConfigurationService(), workspaceService, new TestLifecycleService(), new TestStorageService(), new TestNotificationService(), { disableWatcher: true }); instantiationService.stub(IFileService, fileService); instantiationService.stub(ITextFileService, instantiationService.createInstance(TestTextFileService));