diff --git a/src/vs/platform/userDataSync/common/keybindingsSync.ts b/src/vs/platform/userDataSync/common/keybindingsSync.ts index ff3b25fe604eedc8886e48eed0135d3df8fb90d8..cfafec8c3aecc63cbab9d7a007f7192a2e2c67f1 100644 --- a/src/vs/platform/userDataSync/common/keybindingsSync.ts +++ b/src/vs/platform/userDataSync/common/keybindingsSync.ts @@ -73,8 +73,9 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem let hasConflicts: boolean = false; if (remoteContent) { - const localContent: string = fileContent ? fileContent.value.toString() : '[]'; - if (!localContent.trim() || this.hasErrors(localContent)) { + let localContent: string = fileContent ? fileContent.value.toString() : '[]'; + localContent = localContent || '[]'; + if (this.hasErrors(localContent)) { throw new UserDataSyncError(localize('errorInvalidSettings', "Unable to sync keybindings because the content in the file is not valid. Please open the file and correct it."), UserDataSyncErrorCode.LocalInvalidContent, this.resource); } @@ -177,8 +178,12 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem this.logService.info(`${this.syncResourceLogLabel}: No changes found during synchronizing keybindings.`); } - if (content !== null && this.hasErrors(content)) { - throw new UserDataSyncError(localize('errorInvalidSettings', "Unable to sync keybindings because the content in the file is not valid. Please open the file and correct it."), UserDataSyncErrorCode.LocalInvalidContent, this.resource); + if (content !== null) { + content = content.trim(); + content = content || '[]'; + if (this.hasErrors(content)) { + throw new UserDataSyncError(localize('errorInvalidSettings', "Unable to sync keybindings because the content in the file is not valid. Please open the file and correct it."), UserDataSyncErrorCode.LocalInvalidContent, this.resource); + } } if (localChange !== Change.None) { diff --git a/src/vs/platform/userDataSync/common/settingsSync.ts b/src/vs/platform/userDataSync/common/settingsSync.ts index 44e2941b04ca703542d6b8691a9ff42be019c180..cbba5c25c3dc5d7fb553cc9cdc7af8ffc13e81a7 100644 --- a/src/vs/platform/userDataSync/common/settingsSync.ts +++ b/src/vs/platform/userDataSync/common/settingsSync.ts @@ -78,7 +78,8 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement let hasConflicts: boolean = false; if (remoteSettingsSyncContent) { - const localContent: string = fileContent ? fileContent.value.toString() : '{}'; + let localContent: string = fileContent ? fileContent.value.toString().trim() : '{}'; + localContent = localContent || '{}'; this.validateContent(localContent); this.logService.trace(`${this.syncResourceLogLabel}: Merging remote settings with local settings...`); const result = merge(localContent, remoteSettingsSyncContent.settings, lastSettingsSyncContent ? lastSettingsSyncContent.settings : null, ignoredSettings, [], formattingOptions); @@ -183,7 +184,8 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement this.logService.info(`${this.syncResourceLogLabel}: No changes found during synchronizing settings.`); } - content = content !== null ? content : '{}'; + content = content ? content.trim() : '{}'; + content = content || '{}'; this.validateContent(content); if (localChange !== Change.None) { diff --git a/src/vs/platform/userDataSync/test/common/keybindingsSync.test.ts b/src/vs/platform/userDataSync/test/common/keybindingsSync.test.ts index 49d439bfd608b97b67487378232b1a73a56e97f2..02b0154b62769980ec075ee05b9260f5d168bc74 100644 --- a/src/vs/platform/userDataSync/test/common/keybindingsSync.test.ts +++ b/src/vs/platform/userDataSync/test/common/keybindingsSync.test.ts @@ -61,6 +61,45 @@ suite('KeybindingsSync', () => { assert.deepEqual(server.requests, []); }); + test('when keybindings file is empty and remote has no changes', async () => { + const fileService = client.instantiationService.get(IFileService); + const keybindingsResource = client.instantiationService.get(IEnvironmentService).keybindingsResource; + await fileService.writeFile(keybindingsResource, VSBuffer.fromString('')); + + await testObject.sync(await client.manifest()); + + const lastSyncUserData = await testObject.getLastSyncUserData(); + const remoteUserData = await testObject.getRemoteUserData(null); + assert.equal(testObject.getKeybindingsContentFromSyncContent(lastSyncUserData!.syncData!.content!), '[]'); + assert.equal(testObject.getKeybindingsContentFromSyncContent(remoteUserData!.syncData!.content!), '[]'); + assert.equal((await fileService.readFile(keybindingsResource)).value.toString(), ''); + }); + + test('when keybindings file is empty and remote has changes', async () => { + const client2 = disposableStore.add(new UserDataSyncClient(server)); + await client2.setUp(true); + const content = JSON.stringify([ + { + 'key': 'shift+cmd+w', + 'command': 'workbench.action.closeAllEditors', + } + ]); + await client2.instantiationService.get(IFileService).writeFile(client2.instantiationService.get(IEnvironmentService).keybindingsResource, VSBuffer.fromString(content)); + await client2.sync(); + + const fileService = client.instantiationService.get(IFileService); + const keybindingsResource = client.instantiationService.get(IEnvironmentService).keybindingsResource; + await fileService.writeFile(keybindingsResource, VSBuffer.fromString('')); + + await testObject.sync(await client.manifest()); + + const lastSyncUserData = await testObject.getLastSyncUserData(); + const remoteUserData = await testObject.getRemoteUserData(null); + assert.equal(testObject.getKeybindingsContentFromSyncContent(lastSyncUserData!.syncData!.content!), content); + assert.equal(testObject.getKeybindingsContentFromSyncContent(remoteUserData!.syncData!.content!), content); + assert.equal((await fileService.readFile(keybindingsResource)).value.toString(), content); + }); + test('when keybindings file is created after first sync', async () => { const fileService = client.instantiationService.get(IFileService); const keybindingsResource = client.instantiationService.get(IEnvironmentService).keybindingsResource; diff --git a/src/vs/platform/userDataSync/test/common/settingsSync.test.ts b/src/vs/platform/userDataSync/test/common/settingsSync.test.ts index 3121fb533f8cbe16444532d7ddfa4cfc7510d37f..6117c2b55e269a59a4eaa7452011dcd3e8faa257 100644 --- a/src/vs/platform/userDataSync/test/common/settingsSync.test.ts +++ b/src/vs/platform/userDataSync/test/common/settingsSync.test.ts @@ -79,6 +79,61 @@ suite('SettingsSync - Auto', () => { assert.deepEqual(server.requests, []); }); + test('when settings file is empty and remote has no changes', async () => { + const fileService = client.instantiationService.get(IFileService); + const settingsResource = client.instantiationService.get(IEnvironmentService).settingsResource; + await fileService.writeFile(settingsResource, VSBuffer.fromString('')); + + await testObject.sync(await client.manifest()); + + const lastSyncUserData = await testObject.getLastSyncUserData(); + const remoteUserData = await testObject.getRemoteUserData(null); + assert.equal(testObject.parseSettingsSyncContent(lastSyncUserData!.syncData!.content!)?.settings, '{}'); + assert.equal(testObject.parseSettingsSyncContent(remoteUserData!.syncData!.content!)?.settings, '{}'); + assert.equal((await fileService.readFile(settingsResource)).value.toString(), ''); + }); + + test('when settings file is empty and remote has changes', async () => { + const client2 = disposableStore.add(new UserDataSyncClient(server)); + await client2.setUp(true); + const content = + `{ + // Always + "files.autoSave": "afterDelay", + "files.simpleDialog.enable": true, + + // Workbench + "workbench.colorTheme": "GitHub Sharp", + "workbench.tree.indent": 20, + "workbench.colorCustomizations": { + "editorLineNumber.activeForeground": "#ff0000", + "[GitHub Sharp]": { + "statusBarItem.remoteBackground": "#24292E", + "editorPane.background": "#f3f1f11a" + } + }, + + "gitBranch.base": "remote-repo/master", + + // Experimental + "workbench.view.experimental.allowMovingToNewContainer": true, +}`; + await client2.instantiationService.get(IFileService).writeFile(client2.instantiationService.get(IEnvironmentService).settingsResource, VSBuffer.fromString(content)); + await client2.sync(); + + const fileService = client.instantiationService.get(IFileService); + const settingsResource = client.instantiationService.get(IEnvironmentService).settingsResource; + await fileService.writeFile(settingsResource, VSBuffer.fromString('')); + + await testObject.sync(await client.manifest()); + + const lastSyncUserData = await testObject.getLastSyncUserData(); + const remoteUserData = await testObject.getRemoteUserData(null); + assert.equal(testObject.parseSettingsSyncContent(lastSyncUserData!.syncData!.content!)?.settings, content); + assert.equal(testObject.parseSettingsSyncContent(remoteUserData!.syncData!.content!)?.settings, content); + assert.equal((await fileService.readFile(settingsResource)).value.toString(), content); + }); + test('when settings file is created after first sync', async () => { const fileService = client.instantiationService.get(IFileService);