diff --git a/src/vs/platform/userDataSync/common/abstractSynchronizer.ts b/src/vs/platform/userDataSync/common/abstractSynchronizer.ts index 47b51925609dd514f37abd701af30191e6eb3676..5f9a791d8f8e7d50bdb13d823312024678d5f966 100644 --- a/src/vs/platform/userDataSync/common/abstractSynchronizer.ts +++ b/src/vs/platform/userDataSync/common/abstractSynchronizer.ts @@ -57,8 +57,8 @@ export interface IMergableResourcePreview extends IBaseResourcePreview { readonly remoteContent: string | null; readonly localContent: string | null; readonly previewContent: string | null; + readonly acceptedContent: string | null; readonly hasConflicts: boolean; - mergeState: MergeState; } export type IResourcePreview = Omit; @@ -385,48 +385,58 @@ export abstract class AbstractSynchroniser extends Disposable { } async accept(resource: URI, content: string): Promise { - if (!this.syncPreviewPromise) { - return null; - } - - let preview = await this.syncPreviewPromise; - this.syncPreviewPromise = createCancelablePromise(token => this.updateSyncResourcePreviewContent(preview, resource, content, token)); - - preview = await this.syncPreviewPromise; - this.updateConflicts(preview.resourcePreviews); - if (preview.resourcePreviews.some(({ mergeState }) => mergeState === MergeState.Conflict)) { - this.setStatus(SyncStatus.HasConflicts); - } else { - this.setStatus(SyncStatus.Syncing); - } - - return preview; + await this.updateSyncResourcePreview(resource, async (resourcePreview) => { + const updatedResourcePreview = await this.updateResourcePreview(resourcePreview, resource, content); + return { + ...updatedResourcePreview, + mergeState: MergeState.Accepted + }; + }); + return this.syncPreviewPromise; } async merge(resource: URI): Promise { - await this.changeMergeState(resource, (resourcePreview) => resourcePreview.hasConflicts ? MergeState.Conflict : MergeState.Accepted); + await this.updateSyncResourcePreview(resource, async (resourcePreview) => ({ + ...resourcePreview, + mergeState: resourcePreview.hasConflicts ? MergeState.Conflict : MergeState.Accepted + })); return this.syncPreviewPromise; } async discard(resource: URI): Promise { - await this.changeMergeState(resource, () => MergeState.Preview); + await this.updateSyncResourcePreview(resource, async (resourcePreview) => { + await this.fileService.writeFile(resourcePreview.previewResource, VSBuffer.fromString(resourcePreview.previewContent || '')); + const updatedPreview = await this.updateResourcePreview(resourcePreview, resourcePreview.previewResource, resourcePreview.previewContent || ''); + return { + ...updatedPreview, + mergeState: MergeState.Preview + }; + }); return this.syncPreviewPromise; } - private async changeMergeState(resource: URI, mergeState: (resourcePreview: IMergableResourcePreview) => MergeState): Promise { + private async updateSyncResourcePreview(resource: URI, updateResourcePreview: (resourcePreview: IMergableResourcePreview) => Promise): Promise { if (!this.syncPreviewPromise) { return; } - const preview = await this.syncPreviewPromise; - const resourcePreview = preview.resourcePreviews.find(({ localResource, remoteResource, previewResource }) => + let preview = await this.syncPreviewPromise; + const index = preview.resourcePreviews.findIndex(({ localResource, remoteResource, previewResource }) => isEqual(localResource, resource) || isEqual(remoteResource, resource) || isEqual(previewResource, resource)); - if (!resourcePreview) { + if (index === -1) { return; } - resourcePreview.mergeState = mergeState(resourcePreview); + this.syncPreviewPromise = createCancelablePromise(async token => { + const resourcePreviews = [...preview.resourcePreviews]; + resourcePreviews[index] = await updateResourcePreview(resourcePreviews[index]); + return { + ...preview, + resourcePreviews + }; + }); + preview = await this.syncPreviewPromise; this.updateConflicts(preview.resourcePreviews); if (preview.resourcePreviews.some(({ mergeState }) => mergeState === MergeState.Conflict)) { this.setStatus(SyncStatus.HasConflicts); @@ -435,6 +445,13 @@ export abstract class AbstractSynchroniser extends Disposable { } } + protected async updateResourcePreview(resourcePreview: IResourcePreview, resource: URI, acceptedContent: string): Promise { + return { + ...resourcePreview, + acceptedContent + }; + } + private async doApply(force: boolean): Promise { if (!this.syncPreviewPromise) { return SyncStatus.Idle; @@ -464,32 +481,6 @@ export abstract class AbstractSynchroniser extends Disposable { return SyncStatus.Idle; } - private async updateSyncResourcePreviewContent(preview: ISyncResourcePreview, resource: URI, previewContent: string, token: CancellationToken): Promise { - const index = preview.resourcePreviews.findIndex(({ localResource, remoteResource, previewResource, localChange, remoteChange }) => - (localChange !== Change.None || remoteChange !== Change.None) - && (isEqual(localResource, resource) || isEqual(remoteResource, resource) || isEqual(previewResource, resource))); - if (index !== -1) { - const resourcePreviews = [...preview.resourcePreviews]; - const resourcePreview = await this.updateResourcePreviewContent(resourcePreviews[index], resource, previewContent, token); - resourcePreviews[index] = { ...resourcePreview, mergeState: MergeState.Accepted }; - preview = { - ...preview, - resourcePreviews - }; - } - return preview; - } - - protected async updateResourcePreviewContent(resourcePreview: IResourcePreview, resource: URI, previewContent: string, token: CancellationToken): Promise { - return { - ...resourcePreview, - previewContent, - hasConflicts: false, - localChange: Change.Modified, - remoteChange: Change.Modified, - }; - } - private async clearPreviewFolder(): Promise { try { await this.fileService.del(this.syncPreviewFolder, { recursive: true }); @@ -555,13 +546,13 @@ export abstract class AbstractSynchroniser extends Disposable { const syncPreview = this.syncPreviewPromise ? await this.syncPreviewPromise : null; if (syncPreview) { for (const resourcePreview of syncPreview.resourcePreviews) { - if (resourcePreview.previewResource && isEqual(resourcePreview.previewResource, uri)) { - return resourcePreview.previewContent || ''; + if (isEqual(resourcePreview.acceptedResource, uri)) { + return resourcePreview.acceptedContent || ''; } - if (resourcePreview.remoteResource && isEqual(resourcePreview.remoteResource, uri)) { + if (isEqual(resourcePreview.remoteResource, uri)) { return resourcePreview.remoteContent || ''; } - if (resourcePreview.localResource && isEqual(resourcePreview.localResource, uri)) { + if (isEqual(resourcePreview.localResource, uri)) { return resourcePreview.localContent || ''; } } @@ -581,18 +572,25 @@ export abstract class AbstractSynchroniser extends Disposable { // For preview, use remoteUserData if lastSyncUserData does not exists and last sync is from current machine const lastSyncUserDataForPreview = lastSyncUserData === null && isLastSyncFromCurrentMachine ? remoteUserData : lastSyncUserData; - const resourcePreviews = await this.generateSyncPreview(remoteUserData, lastSyncUserDataForPreview, token); - - // Mark merge - const mergableResourcePreviews = resourcePreviews.map(r => ({ - ...r, - mergeState: r.localChange === Change.None && r.remoteChange === Change.None ? MergeState.Accepted /* Mark previews with no changes as merged */ - : apply ? (r.hasConflicts ? MergeState.Conflict : MergeState.Accepted) - : MergeState.Preview + const result = await this.generateSyncPreview(remoteUserData, lastSyncUserDataForPreview, token); - })); + const resourcePreviews: IMergableResourcePreview[] = []; + for (const resourcePreview of result) { + if (token.isCancellationRequested) { + break; + } + if (!apply) { + await this.fileService.writeFile(resourcePreview.previewResource, VSBuffer.fromString(resourcePreview.previewContent || '')); + } + resourcePreviews.push({ + ...resourcePreview, + mergeState: resourcePreview.localChange === Change.None && resourcePreview.remoteChange === Change.None ? MergeState.Accepted /* Mark previews with no changes as merged */ + : apply ? (resourcePreview.hasConflicts ? MergeState.Conflict : MergeState.Accepted) + : MergeState.Preview + }); + } - return { remoteUserData, lastSyncUserData, resourcePreviews: mergableResourcePreviews, isLastSyncFromCurrentMachine }; + return { remoteUserData, lastSyncUserData, resourcePreviews, isLastSyncFromCurrentMachine }; } async getLastSyncUserData(): Promise { diff --git a/src/vs/platform/userDataSync/common/extensionsSync.ts b/src/vs/platform/userDataSync/common/extensionsSync.ts index 97c6d29749f2dcc54e5740ad67ae69d32f4b5471..15d2a44190bfb77ddf497aa5c268d5fc7521ba3e 100644 --- a/src/vs/platform/userDataSync/common/extensionsSync.ts +++ b/src/vs/platform/userDataSync/common/extensionsSync.ts @@ -47,7 +47,8 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse protected readonly version: number = 3; protected isEnabled(): boolean { return super.isEnabled() && this.extensionGalleryService.isEnabled(); } private readonly localPreviewResource: URI = joinPath(this.syncPreviewFolder, 'extensions.json'); - private readonly remotePreviewResource: URI = this.localPreviewResource.with({ scheme: USER_DATA_SYNC_SCHEME }); + private readonly remotePreviewResource: URI = this.localPreviewResource.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' }); + private readonly acceptedPreviewResource: URI = this.localPreviewResource.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' }); constructor( @IEnvironmentService environmentService: IEnvironmentService, @@ -100,6 +101,8 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse remoteContent: remoteExtensions ? this.format(remoteExtensions) : null, previewResource: this.localPreviewResource, previewContent: null, + acceptedResource: this.acceptedPreviewResource, + acceptedContent: null, added, removed, updated, @@ -137,6 +140,8 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse remoteContent: remoteExtensions ? this.format(remoteExtensions) : null, previewResource: this.localPreviewResource, previewContent: null, + acceptedResource: this.acceptedPreviewResource, + acceptedContent: null, added, removed, updated, @@ -177,14 +182,14 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse } } - protected async updateResourcePreviewContent(resourcePreview: IExtensionResourcePreview, resource: URI, previewContent: string, token: CancellationToken): Promise { + protected async updateResourcePreview(resourcePreview: IExtensionResourcePreview, resource: URI, acceptedContent: string): Promise { if (isEqual(resource, ExtensionsSynchroniser.EXTENSIONS_DATA_URI)) { const remoteExtensions = resourcePreview.remoteContent ? JSON.parse(resourcePreview.remoteContent) : null; return this.getPushPreview(remoteExtensions); } return { ...resourcePreview, - previewContent, + acceptedContent, hasConflicts: false, localChange: Change.Modified, remoteChange: Change.Modified, @@ -199,6 +204,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse const localContent = this.format(localExtensions); const remoteResource = this.remotePreviewResource; const previewResource = this.localPreviewResource; + const acceptedResource = this.acceptedPreviewResource; const previewContent = null; if (remoteExtensions !== null) { const mergeResult = merge(localExtensions, remoteExtensions, localExtensions, [], ignoredExtensions); @@ -210,6 +216,8 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse remoteContent: this.format(remoteExtensions), previewResource, previewContent, + acceptedResource, + acceptedContent: previewContent, added, removed, updated, @@ -228,6 +236,8 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse remoteContent: null, previewResource, previewContent, + acceptedResource, + acceptedContent: previewContent, added: [], removed: [], updated: [], remote: null, localExtensions, skippedExtensions: [], localChange: Change.None, remoteChange: Change.None, @@ -249,6 +259,8 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse remoteContent: remoteExtensions ? this.format(remoteExtensions) : null, previewResource: this.localPreviewResource, previewContent: null, + acceptedResource: this.acceptedPreviewResource, + acceptedContent: null, added, removed, updated, @@ -272,7 +284,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse return this.format(localExtensions); } - if (isEqual(this.remotePreviewResource, uri) || isEqual(this.localPreviewResource, uri)) { + if (isEqual(this.remotePreviewResource, uri) || isEqual(this.acceptedPreviewResource, uri)) { return this.resolvePreviewContent(uri); } diff --git a/src/vs/platform/userDataSync/common/globalStateSync.ts b/src/vs/platform/userDataSync/common/globalStateSync.ts index 8d32b1ab4d843574fe629e24af7ac16951367862..2478f8b81fe32756ecd3367dedabc451f91162d3 100644 --- a/src/vs/platform/userDataSync/common/globalStateSync.ts +++ b/src/vs/platform/userDataSync/common/globalStateSync.ts @@ -46,7 +46,8 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs private static readonly GLOBAL_STATE_DATA_URI = URI.from({ scheme: USER_DATA_SYNC_SCHEME, authority: 'globalState', path: `/globalState.json` }); protected readonly version: number = 1; private readonly localPreviewResource: URI = joinPath(this.syncPreviewFolder, 'globalState.json'); - private readonly remotePreviewResource: URI = this.localPreviewResource.with({ scheme: USER_DATA_SYNC_SCHEME }); + private readonly remotePreviewResource: URI = this.localPreviewResource.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' }); + private readonly acceptedPreviewResource: URI = this.localPreviewResource.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' }); constructor( @IFileService fileService: IFileService, @@ -99,6 +100,8 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs remoteContent: remoteGlobalState ? this.format(remoteGlobalState) : null, previewResource: this.localPreviewResource, previewContent: null, + acceptedResource: this.acceptedPreviewResource, + acceptedContent: null, local, remote: syncGlobalState.storage, localUserData, @@ -131,6 +134,8 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs remoteContent: remoteGlobalState ? this.format(remoteGlobalState) : null, previewResource: this.localPreviewResource, previewContent: null, + acceptedResource: this.acceptedPreviewResource, + acceptedContent: null, local, remote, localUserData: localGloablState, @@ -172,7 +177,7 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs } } - protected async updateResourcePreviewContent(resourcePreview: IGlobalStateResourcePreview, resource: URI, previewContent: string, token: CancellationToken): Promise { + protected async updateResourcePreview(resourcePreview: IGlobalStateResourcePreview, resource: URI, acceptedContent: string): Promise { if (GlobalStateSynchroniser.GLOBAL_STATE_DATA_URI, resource) { return this.getPushPreview(resourcePreview.remoteContent); } @@ -188,6 +193,7 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs const localContent = this.format(localGlobalState); const remoteResource = this.remotePreviewResource; const previewResource = this.localPreviewResource; + const acceptedResource = this.acceptedPreviewResource; const previewContent = null; if (remoteContent !== null) { const remoteGlobalState: IGlobalState = JSON.parse(remoteContent); @@ -200,6 +206,8 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs remoteContent: this.format(remoteGlobalState), previewResource, previewContent, + acceptedResource, + acceptedContent: previewContent, local, remote, localUserData: localGlobalState, @@ -216,6 +224,8 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs remoteContent: null, previewResource, previewContent, + acceptedResource, + acceptedContent: previewContent, local: { added: {}, removed: [], updated: {} }, remote: null, localUserData: localGlobalState, @@ -237,6 +247,8 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs remoteContent: remoteGlobalState ? this.format(remoteGlobalState) : null, previewResource: this.localPreviewResource, previewContent: null, + acceptedResource: this.acceptedPreviewResource, + acceptedContent: null, local: { added: {}, removed: [], updated: {} }, remote: localUserData.storage, localUserData, @@ -257,7 +269,7 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs return this.format(localGlobalState); } - if (isEqual(this.remotePreviewResource, uri) || isEqual(this.localPreviewResource, uri)) { + if (isEqual(this.remotePreviewResource, uri) || isEqual(this.acceptedPreviewResource, uri)) { return this.resolvePreviewContent(uri); } diff --git a/src/vs/platform/userDataSync/common/keybindingsSync.ts b/src/vs/platform/userDataSync/common/keybindingsSync.ts index 640d85335689f0546f4ce3710a091a08ef118c85..78701c2719dd89078420fe9292dcce35b4e123e1 100644 --- a/src/vs/platform/userDataSync/common/keybindingsSync.ts +++ b/src/vs/platform/userDataSync/common/keybindingsSync.ts @@ -36,7 +36,8 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem protected readonly version: number = 1; protected readonly localPreviewResource: URI = joinPath(this.syncPreviewFolder, 'keybindings.json'); - protected readonly remotePreviewResource: URI = this.localPreviewResource.with({ scheme: USER_DATA_SYNC_SCHEME }); + protected readonly remotePreviewResource: URI = this.localPreviewResource.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' }); + private readonly acceptPreviewResource: URI = this.localPreviewResource.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' }); constructor( @IUserDataSyncStoreService userDataSyncStoreService: IUserDataSyncStoreService, @@ -65,6 +66,8 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem remoteContent: previewContent, previewResource: this.localPreviewResource, previewContent, + acceptedResource: this.acceptPreviewResource, + acceptedContent: previewContent, localChange: previewContent !== null ? Change.Modified : Change.None, remoteChange: Change.None, hasConflicts: false, @@ -83,6 +86,8 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem remoteContent: remoteUserData.syncData !== null ? this.getKeybindingsContentFromSyncContent(remoteUserData.syncData.content) : null, previewResource: this.localPreviewResource, previewContent, + acceptedResource: this.acceptPreviewResource, + acceptedContent: previewContent, localChange: Change.None, remoteChange: previewContent !== null ? Change.Modified : Change.None, hasConflicts: false, @@ -101,6 +106,8 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem remoteContent: remoteUserData.syncData !== null ? this.getKeybindingsContentFromSyncContent(remoteUserData.syncData.content) : null, previewResource: this.localPreviewResource, previewContent, + acceptedResource: this.acceptPreviewResource, + acceptedContent: previewContent, localChange: previewContent !== null ? Change.Modified : Change.None, remoteChange: previewContent !== null ? Change.Modified : Change.None, hasConflicts: false, @@ -161,6 +168,8 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem remoteContent, previewResource: this.localPreviewResource, previewContent, + acceptedResource: this.acceptPreviewResource, + acceptedContent: previewContent, hasConflicts, localChange: hasLocalChanged ? fileContent ? Change.Modified : Change.Added : Change.None, remoteChange: hasRemoteChanged ? Change.Modified : Change.None, @@ -168,7 +177,7 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem } protected async applyPreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, resourcePreviews: IFileResourcePreview[], force: boolean): Promise { - let { fileContent, previewContent: content, localChange, remoteChange } = resourcePreviews[0]; + let { fileContent, acceptedContent: content, localChange, remoteChange } = resourcePreviews[0]; if (content !== null) { if (this.hasErrors(content)) { @@ -234,7 +243,7 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem const fileContent = await this.getLocalFileContent(); return fileContent ? fileContent.value.toString() : ''; } - if (isEqual(this.remotePreviewResource, uri) || isEqual(this.localPreviewResource, uri)) { + if (isEqual(this.remotePreviewResource, uri) || isEqual(this.acceptPreviewResource, uri)) { return this.resolvePreviewContent(uri); } let content = await super.resolveContent(uri); diff --git a/src/vs/platform/userDataSync/common/settingsSync.ts b/src/vs/platform/userDataSync/common/settingsSync.ts index c60a364a9ab19b21e5792048ccfd1fb05e004aee..011473ce16d04d6ed5553aade60a29ccf68eda12 100644 --- a/src/vs/platform/userDataSync/common/settingsSync.ts +++ b/src/vs/platform/userDataSync/common/settingsSync.ts @@ -39,8 +39,9 @@ function isSettingsSyncContent(thing: any): thing is ISettingsSyncContent { export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implements IUserDataSynchroniser { protected readonly version: number = 1; - protected readonly localPreviewResource: URI = joinPath(this.syncPreviewFolder, 'settings.json'); - protected readonly remotePreviewResource: URI = this.localPreviewResource.with({ scheme: USER_DATA_SYNC_SCHEME }); + private readonly localPreviewResource: URI = joinPath(this.syncPreviewFolder, 'settings.json'); + private readonly remotePreviewResource: URI = this.localPreviewResource.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' }); + private readonly acceptPreviewResource: URI = this.localPreviewResource.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' }); constructor( @IFileService fileService: IFileService, @@ -79,6 +80,8 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement remoteContent: remoteSettingsSyncContent ? remoteSettingsSyncContent.settings : null, previewResource: this.localPreviewResource, previewContent, + acceptedResource: this.acceptPreviewResource, + acceptedContent: previewContent, localChange: previewContent !== null ? Change.Modified : Change.None, remoteChange: Change.None, hasConflicts: false, @@ -106,6 +109,8 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement remoteContent: remoteSettingsSyncContent ? remoteSettingsSyncContent.settings : null, previewResource: this.localPreviewResource, previewContent, + acceptedResource: this.acceptPreviewResource, + acceptedContent: previewContent, localChange: Change.None, remoteChange: previewContent !== null ? Change.Modified : Change.None, hasConflicts: false, @@ -133,6 +138,8 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement remoteContent: remoteSettingsSyncContent ? remoteSettingsSyncContent.settings : null, previewResource: this.localPreviewResource, previewContent, + acceptedResource: this.acceptPreviewResource, + acceptedContent: previewContent, localChange: previewContent !== null ? Change.Modified : Change.None, remoteChange: previewContent !== null ? Change.Modified : Change.None, hasConflicts: false, @@ -146,6 +153,7 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement const lastSettingsSyncContent: ISettingsSyncContent | null = lastSyncUserData ? this.getSettingsSyncContent(lastSyncUserData) : null; const ignoredSettings = await this.getIgnoredSettings(); + let acceptedContent: string | null = null; let previewContent: string | null = null; let hasLocalChanged: boolean = false; let hasRemoteChanged: boolean = false; @@ -156,7 +164,7 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement 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); - previewContent = result.localContent || result.remoteContent; + acceptedContent = result.localContent || result.remoteContent; hasLocalChanged = result.localContent !== null; hasRemoteChanged = result.remoteContent !== null; hasConflicts = result.hasConflicts; @@ -165,14 +173,13 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement // First time syncing to remote else if (fileContent) { this.logService.trace(`${this.syncResourceLogLabel}: Remote settings does not exist. Synchronizing settings for the first time.`); - previewContent = fileContent.value.toString(); + acceptedContent = fileContent.value.toString(); hasRemoteChanged = true; } - if (previewContent && !token.isCancellationRequested) { + if (acceptedContent && !token.isCancellationRequested) { // Remove the ignored settings from the preview. - const content = updateIgnoredSettings(previewContent, '{}', ignoredSettings, formattingOptions); - await this.fileService.writeFile(this.localPreviewResource, VSBuffer.fromString(content)); + previewContent = updateIgnoredSettings(acceptedContent, '{}', ignoredSettings, formattingOptions); } return [{ @@ -183,22 +190,26 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement remoteContent: remoteSettingsSyncContent ? remoteSettingsSyncContent.settings : null, previewResource: this.localPreviewResource, previewContent, + acceptedResource: this.acceptPreviewResource, + acceptedContent, localChange: hasLocalChanged ? fileContent ? Change.Modified : Change.Added : Change.None, remoteChange: hasRemoteChanged ? Change.Modified : Change.None, hasConflicts, }]; } - protected async updateResourcePreviewContent(resourcePreview: IFileResourcePreview, resource: URI, previewContent: string, token: CancellationToken): Promise { - const formatUtils = await this.getFormattingOptions(); - // Add ignored settings from local file content - const ignoredSettings = await this.getIgnoredSettings(); - previewContent = updateIgnoredSettings(previewContent, resourcePreview.fileContent ? resourcePreview.fileContent.value.toString() : '{}', ignoredSettings, formatUtils); - return super.updateResourcePreviewContent(resourcePreview, resource, previewContent, token) as Promise; + protected async updateResourcePreview(resourcePreview: IFileResourcePreview, resource: URI, acceptedContent: string): Promise { + if (isEqual(resource, this.localPreviewResource) || isEqual(resource, this.remotePreviewResource)) { + const formatUtils = await this.getFormattingOptions(); + // Add ignored settings from local file content + const ignoredSettings = await this.getIgnoredSettings(); + acceptedContent = updateIgnoredSettings(acceptedContent, resourcePreview.fileContent ? resourcePreview.fileContent.value.toString() : '{}', ignoredSettings, formatUtils); + } + return super.updateResourcePreview(resourcePreview, resource, acceptedContent) as Promise; } protected async applyPreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, resourcePreviews: IFileResourcePreview[], force: boolean): Promise { - let { fileContent, previewContent: content, localChange, remoteChange } = resourcePreviews[0]; + let { fileContent, acceptedContent: content, localChange, remoteChange } = resourcePreviews[0]; if (content !== null) { @@ -264,7 +275,7 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement const fileContent = await this.getLocalFileContent(); return fileContent ? fileContent.value.toString() : ''; } - if (isEqual(this.remotePreviewResource, uri) || isEqual(this.localPreviewResource, uri)) { + if (isEqual(this.remotePreviewResource, uri) || isEqual(this.acceptPreviewResource, uri)) { return this.resolvePreviewContent(uri); } let content = await super.resolveContent(uri); diff --git a/src/vs/platform/userDataSync/common/snippetsSync.ts b/src/vs/platform/userDataSync/common/snippetsSync.ts index ea13a730edba4308f3db08b8d90d5e30b596cd5b..ce0da2144f4c11de9cef88ac547da2458f0f1e20 100644 --- a/src/vs/platform/userDataSync/common/snippetsSync.ts +++ b/src/vs/platform/userDataSync/common/snippetsSync.ts @@ -5,7 +5,7 @@ import { IUserDataSyncStoreService, IUserDataSyncLogService, IUserDataSynchroniser, SyncResource, IUserDataSyncResourceEnablementService, IUserDataSyncBackupStoreService, - USER_DATA_SYNC_SCHEME, ISyncResourceHandle, IRemoteUserData, ISyncData, UserDataSyncError, UserDataSyncErrorCode, Change + USER_DATA_SYNC_SCHEME, ISyncResourceHandle, IRemoteUserData, ISyncData, Change } from 'vs/platform/userDataSync/common/userDataSync'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IFileService, FileChangesEvent, IFileStat, IFileContent, FileOperationError, FileOperationResult } from 'vs/platform/files/common/files'; @@ -20,7 +20,6 @@ import { merge, IMergeResult, areSame } from 'vs/platform/userDataSync/common/sn import { CancellationToken } from 'vs/base/common/cancellation'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { deepClone } from 'vs/base/common/objects'; -import { localize } from 'vs/nls'; export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserDataSynchroniser { @@ -94,34 +93,19 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD } const mergeResult = merge(localSnippets, remoteSnippets, lastSyncSnippets); - const resourcePreviews = this.getResourcePreviews(mergeResult, local, remoteSnippets || {}); - - for (const resourcePreview of resourcePreviews) { - if (resourcePreview.hasConflicts) { - if (!token.isCancellationRequested) { - await this.fileService.writeFile(resourcePreview.previewResource!, VSBuffer.fromString(resourcePreview.previewContent || '')); - } - } - } - - return resourcePreviews; + return this.getResourcePreviews(mergeResult, local, remoteSnippets || {}); } - protected async updateResourcePreviewContent(resourcePreview: IFileResourcePreview, resource: URI, previewContent: string, token: CancellationToken): Promise { + protected async updateResourcePreview(resourcePreview: IFileResourcePreview, resource: URI, acceptedContent: string): Promise { return { ...resourcePreview, - previewContent: previewContent || null, - hasConflicts: false, - localChange: previewContent ? Change.Modified : Change.Deleted, - remoteChange: previewContent ? Change.Modified : Change.Deleted, + acceptedContent: acceptedContent || null, + localChange: acceptedContent ? Change.Modified : Change.Deleted, + remoteChange: acceptedContent ? Change.Modified : Change.Deleted, }; } protected async applyPreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, resourcePreviews: IFileResourcePreview[], force: boolean): Promise { - if (resourcePreviews.some(({ hasConflicts }) => hasConflicts)) { - throw new UserDataSyncError(localize('unresolved conflicts', "Error while syncing {0}. Please resolve conflicts first.", this.syncResourceLogLabel), UserDataSyncErrorCode.UnresolvedConflicts, this.resource); - } - if (resourcePreviews.every(({ localChange, remoteChange }) => localChange === Change.None && remoteChange === Change.None)) { this.logService.info(`${this.syncResourceLogLabel}: No changes found during synchronizing snippets.`); } @@ -161,10 +145,12 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD localResource: joinPath(this.snippetsFolder, key), fileContent: null, localContent: null, - remoteResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME }), + remoteResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' }), remoteContent: remoteSnippets[key], previewResource: joinPath(this.syncPreviewFolder, key), previewContent: mergeResult.local.added[key], + acceptedResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' }), + acceptedContent: mergeResult.local.added[key], hasConflicts: false, localChange: Change.Added, remoteChange: Change.None @@ -177,10 +163,12 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD localResource: joinPath(this.snippetsFolder, key), fileContent: localFileContent[key], localContent: localFileContent[key].value.toString(), - remoteResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME }), + remoteResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' }), remoteContent: remoteSnippets[key], previewResource: joinPath(this.syncPreviewFolder, key), previewContent: mergeResult.local.updated[key], + acceptedResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' }), + acceptedContent: mergeResult.local.updated[key], hasConflicts: false, localChange: Change.Modified, remoteChange: Change.None @@ -193,10 +181,12 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD localResource: joinPath(this.snippetsFolder, key), fileContent: localFileContent[key], localContent: localFileContent[key].value.toString(), - remoteResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME }), + remoteResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' }), remoteContent: null, previewResource: joinPath(this.syncPreviewFolder, key), previewContent: null, + acceptedResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' }), + acceptedContent: null, hasConflicts: false, localChange: Change.Deleted, remoteChange: Change.None @@ -209,10 +199,12 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD localResource: joinPath(this.snippetsFolder, key), fileContent: localFileContent[key], localContent: localFileContent[key].value.toString(), - remoteResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME }), + remoteResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' }), remoteContent: null, previewResource: joinPath(this.syncPreviewFolder, key), previewContent: mergeResult.remote.added[key], + acceptedResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' }), + acceptedContent: mergeResult.remote.added[key], hasConflicts: false, localChange: Change.None, remoteChange: Change.Added @@ -225,10 +217,12 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD localResource: joinPath(this.snippetsFolder, key), fileContent: localFileContent[key], localContent: localFileContent[key].value.toString(), - remoteResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME }), + remoteResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' }), remoteContent: remoteSnippets[key], previewResource: joinPath(this.syncPreviewFolder, key), previewContent: mergeResult.remote.updated[key], + acceptedResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' }), + acceptedContent: mergeResult.remote.updated[key], hasConflicts: false, localChange: Change.None, remoteChange: Change.Modified @@ -241,10 +235,12 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD localResource: joinPath(this.snippetsFolder, key), fileContent: null, localContent: null, - remoteResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME }), + remoteResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' }), remoteContent: remoteSnippets[key], previewResource: joinPath(this.syncPreviewFolder, key), previewContent: null, + acceptedResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' }), + acceptedContent: null, hasConflicts: false, localChange: Change.None, remoteChange: Change.Deleted @@ -257,10 +253,12 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD localResource: joinPath(this.snippetsFolder, key), fileContent: localFileContent[key] || null, localContent: localFileContent[key] ? localFileContent[key].value.toString() : null, - remoteResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME }), + remoteResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' }), remoteContent: remoteSnippets[key] || null, previewResource: joinPath(this.syncPreviewFolder, key), previewContent: localFileContent[key] ? localFileContent[key].value.toString() : null, + acceptedResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' }), + acceptedContent: localFileContent[key] ? localFileContent[key].value.toString() : null, hasConflicts: true, localChange: localFileContent[key] ? Change.Modified : Change.Added, remoteChange: remoteSnippets[key] ? Change.Modified : Change.Added @@ -274,10 +272,12 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD localResource: joinPath(this.snippetsFolder, key), fileContent: localFileContent[key] || null, localContent: localFileContent[key] ? localFileContent[key].value.toString() : null, - remoteResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME }), + remoteResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' }), remoteContent: remoteSnippets[key] || null, previewResource: joinPath(this.syncPreviewFolder, key), previewContent: localFileContent[key] ? localFileContent[key].value.toString() : null, + acceptedResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' }), + acceptedContent: localFileContent[key] ? localFileContent[key].value.toString() : null, hasConflicts: false, localChange: Change.None, remoteChange: Change.None @@ -317,8 +317,8 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD } } - if (isEqualOrParent(uri.with({ scheme: this.syncPreviewFolder.scheme }), this.syncPreviewFolder) - || isEqualOrParent(uri, this.syncPreviewFolder.with({ scheme: USER_DATA_SYNC_SCHEME }))) { + if (isEqualOrParent(uri, this.syncPreviewFolder.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' })) + || isEqualOrParent(uri, this.syncPreviewFolder.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' }))) { return this.resolvePreviewContent(uri); } @@ -362,12 +362,7 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD } private async updateLocalSnippets(resourcePreviews: IFileResourcePreview[], force: boolean): Promise { - if (resourcePreviews.some(({ hasConflicts }) => hasConflicts)) { - // Do not update if there are conflicts - return; - } - - for (const { fileContent, previewContent: content, localResource, remoteResource, localChange } of resourcePreviews) { + for (const { fileContent, acceptedContent: content, localResource, remoteResource, localChange } of resourcePreviews) { if (localChange !== Change.None) { const key = remoteResource ? basename(remoteResource) : basename(localResource!); const resource = joinPath(this.snippetsFolder, key); @@ -397,15 +392,10 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD } private async updateRemoteSnippets(resourcePreviews: IFileResourcePreview[], remoteUserData: IRemoteUserData, forcePush: boolean): Promise { - if (resourcePreviews.some(({ hasConflicts }) => hasConflicts)) { - // Do not update if there are conflicts - return remoteUserData; - } - const currentSnippets: IStringDictionary = remoteUserData.syncData ? this.parseSnippets(remoteUserData.syncData) : {}; const newSnippets: IStringDictionary = deepClone(currentSnippets); - for (const { previewContent: content, localResource, remoteResource, remoteChange } of resourcePreviews) { + for (const { acceptedContent: content, localResource, remoteResource, remoteChange } of resourcePreviews) { if (remoteChange !== Change.None) { const key = localResource ? basename(localResource) : basename(remoteResource!); if (remoteChange === Change.Deleted) { diff --git a/src/vs/platform/userDataSync/common/userDataSync.ts b/src/vs/platform/userDataSync/common/userDataSync.ts index 419cbe4fd90887ed660a1cd5882d111dcd9ad5a9..0cd0adf60498875088e31bbfbdc59b648332d5ba 100644 --- a/src/vs/platform/userDataSync/common/userDataSync.ts +++ b/src/vs/platform/userDataSync/common/userDataSync.ts @@ -327,6 +327,7 @@ export interface IResourcePreview { readonly remoteResource: URI; readonly localResource: URI; readonly previewResource: URI; + readonly acceptedResource: URI; readonly localChange: Change; readonly remoteChange: Change; readonly mergeState: MergeState; diff --git a/src/vs/platform/userDataSync/common/userDataSyncService.ts b/src/vs/platform/userDataSync/common/userDataSyncService.ts index 766d59b9918a0f33701c10e2d23360806a2f98cd..7e3485683cc60edf87e74a7da0f35d7f04dadf4d 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncService.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncService.ts @@ -641,6 +641,7 @@ function toStrictResourcePreview(resourcePreview: IResourcePreview): IResourcePr localResource: resourcePreview.localResource, previewResource: resourcePreview.previewResource, remoteResource: resourcePreview.remoteResource, + acceptedResource: resourcePreview.acceptedResource, localChange: resourcePreview.localChange, remoteChange: resourcePreview.remoteChange, mergeState: resourcePreview.mergeState, diff --git a/src/vs/platform/userDataSync/test/common/synchronizer.test.ts b/src/vs/platform/userDataSync/test/common/synchronizer.test.ts index 95677888d25e53871ac5571eb309412a8f96396e..0d5106c1945cc1306b834292774d1b37d0e5f764 100644 --- a/src/vs/platform/userDataSync/test/common/synchronizer.test.ts +++ b/src/vs/platform/userDataSync/test/common/synchronizer.test.ts @@ -12,6 +12,8 @@ import { Barrier } from 'vs/base/common/async'; import { Emitter, Event } from 'vs/base/common/event'; import { CancellationToken } from 'vs/base/common/cancellation'; import { URI } from 'vs/base/common/uri'; +import { IFileService } from 'vs/platform/files/common/files'; +import { InMemoryFileSystemProvider } from 'vs/platform/files/common/inMemoryFilesystemProvider'; const resource = URI.from({ scheme: USER_DATA_SYNC_SCHEME, authority: 'testResource', path: `/current.json` }); @@ -47,27 +49,27 @@ class TestSynchroniser extends AbstractSynchroniser { } protected async generatePullPreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, token: CancellationToken): Promise { - return [{ localContent: null, localResource: resource, remoteContent: null, remoteResource: resource, previewContent: remoteUserData.ref, previewResource: resource, localChange: Change.Modified, remoteChange: Change.None, hasConflicts: this.syncResult.hasConflicts }]; + return [{ localContent: null, localResource: resource, remoteContent: null, remoteResource: resource, acceptedContent: remoteUserData.ref, previewContent: remoteUserData.ref, previewResource: resource, acceptedResource: resource, localChange: Change.Modified, remoteChange: Change.None, hasConflicts: this.syncResult.hasConflicts }]; } protected async generatePushPreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, token: CancellationToken): Promise { - return [{ localContent: null, localResource: resource, remoteContent: null, remoteResource: resource, previewContent: remoteUserData.ref, previewResource: resource, localChange: Change.Modified, remoteChange: Change.None, hasConflicts: this.syncResult.hasConflicts }]; + return [{ localContent: null, localResource: resource, remoteContent: null, remoteResource: resource, acceptedContent: remoteUserData.ref, previewContent: remoteUserData.ref, previewResource: resource, acceptedResource: resource, localChange: Change.Modified, remoteChange: Change.None, hasConflicts: this.syncResult.hasConflicts }]; } protected async generateReplacePreview(syncData: ISyncData, remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null): Promise { - return [{ localContent: null, localResource: resource, remoteContent: null, remoteResource: resource, previewContent: remoteUserData.ref, previewResource: resource, localChange: Change.Modified, remoteChange: Change.None, hasConflicts: this.syncResult.hasConflicts }]; + return [{ localContent: null, localResource: resource, remoteContent: null, remoteResource: resource, acceptedContent: remoteUserData.ref, previewContent: remoteUserData.ref, previewResource: resource, acceptedResource: resource, localChange: Change.Modified, remoteChange: Change.None, hasConflicts: this.syncResult.hasConflicts }]; } protected async generateSyncPreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, token: CancellationToken): Promise { if (this.syncResult.hasError) { throw new Error('failed'); } - return [{ localContent: null, localResource: resource, remoteContent: null, remoteResource: resource, previewContent: remoteUserData.ref, previewResource: resource, localChange: Change.Modified, remoteChange: Change.None, hasConflicts: this.syncResult.hasConflicts }]; + return [{ localContent: null, localResource: resource, remoteContent: null, remoteResource: resource, acceptedContent: remoteUserData.ref, previewContent: remoteUserData.ref, previewResource: resource, acceptedResource: resource, localChange: Change.Modified, remoteChange: Change.None, hasConflicts: this.syncResult.hasConflicts }]; } protected async applyPreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, preview: IResourcePreview[], forcePush: boolean): Promise { - if (preview[0]?.previewContent) { - await this.applyRef(preview[0].previewContent); + if (preview[0]?.acceptedContent) { + await this.applyRef(preview[0].acceptedContent); } } @@ -106,6 +108,7 @@ suite('TestSynchronizer', () => { await client.setUp(); userDataSyncStoreService = client.instantiationService.get(IUserDataSyncStoreService); disposableStore.add(toDisposable(() => userDataSyncStoreService.clear())); + client.instantiationService.get(IFileService).registerProvider(USER_DATA_SYNC_SCHEME, new InMemoryFileSystemProvider()); }); teardown(() => disposableStore.clear()); @@ -325,7 +328,7 @@ suite('TestSynchronizer', () => { testObject.syncBarrier.open(); let preview = await testObject.preview(await client.manifest()); - preview = await testObject.accept(preview!.resourcePreviews[0].previewResource, preview!.resourcePreviews[0].previewContent!); + preview = await testObject.accept(preview!.resourcePreviews[0].previewResource, preview!.resourcePreviews[0].acceptedContent!); assert.deepEqual(testObject.status, SyncStatus.Syncing); assertPreviews(preview!.resourcePreviews, [resource]); @@ -338,7 +341,7 @@ suite('TestSynchronizer', () => { testObject.syncBarrier.open(); let preview = await testObject.preview(await client.manifest()); - preview = await testObject.accept(preview!.resourcePreviews[0].previewResource, preview!.resourcePreviews[0].previewContent!); + preview = await testObject.accept(preview!.resourcePreviews[0].previewResource, preview!.resourcePreviews[0].acceptedContent!); preview = await testObject.apply(false); assert.deepEqual(testObject.status, SyncStatus.Idle); @@ -363,8 +366,8 @@ suite('TestSynchronizer', () => { testObject.syncResult = { hasConflicts: true, hasError: false }; testObject.syncBarrier.open(); - const preview = await testObject.preview(await client.manifest()); - await testObject.merge(preview!.resourcePreviews[0].previewResource); + let preview = await testObject.preview(await client.manifest()); + preview = await testObject.merge(preview!.resourcePreviews[0].previewResource); assert.deepEqual(testObject.status, SyncStatus.HasConflicts); assertPreviews(preview!.resourcePreviews, [resource]); @@ -394,7 +397,7 @@ suite('TestSynchronizer', () => { let preview = await testObject.preview(await client.manifest()); await testObject.merge(preview!.resourcePreviews[0].previewResource); - preview = await testObject.accept(preview!.resourcePreviews[0].previewResource, preview!.resourcePreviews[0].previewContent!); + preview = await testObject.accept(preview!.resourcePreviews[0].previewResource, preview!.resourcePreviews[0].acceptedContent!); assert.deepEqual(testObject.status, SyncStatus.Syncing); assertPreviews(preview!.resourcePreviews, [resource]); @@ -408,7 +411,7 @@ suite('TestSynchronizer', () => { let preview = await testObject.preview(await client.manifest()); await testObject.merge(preview!.resourcePreviews[0].previewResource); - preview = await testObject.accept(preview!.resourcePreviews[0].previewResource, preview!.resourcePreviews[0].previewContent!); + preview = await testObject.accept(preview!.resourcePreviews[0].previewResource, preview!.resourcePreviews[0].acceptedContent!); preview = await testObject.apply(false); assert.deepEqual(testObject.status, SyncStatus.Idle); @@ -422,7 +425,7 @@ suite('TestSynchronizer', () => { testObject.syncBarrier.open(); let preview = await testObject.preview(await client.manifest()); - preview = await testObject.accept(preview!.resourcePreviews[0].previewResource, preview!.resourcePreviews[0].previewContent!); + preview = await testObject.accept(preview!.resourcePreviews[0].previewResource, preview!.resourcePreviews[0].acceptedContent!); assert.deepEqual(testObject.status, SyncStatus.Syncing); assertPreviews(preview!.resourcePreviews, [resource]); @@ -435,7 +438,7 @@ suite('TestSynchronizer', () => { testObject.syncBarrier.open(); let preview = await testObject.preview(await client.manifest()); - preview = await testObject.accept(preview!.resourcePreviews[0].previewResource, preview!.resourcePreviews[0].previewContent!); + preview = await testObject.accept(preview!.resourcePreviews[0].previewResource, preview!.resourcePreviews[0].acceptedContent!); preview = await testObject.apply(false); assert.deepEqual(testObject.status, SyncStatus.Idle); diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataManualSyncView.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataManualSyncView.ts index 303347ffe58389f6e705f98268ed81b6c1ed37e8..3f7f4393a7c33c19a12f5b972226ba14c2bf9ac2 100644 --- a/src/vs/workbench/contrib/userDataSync/browser/userDataManualSyncView.ts +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataManualSyncView.ts @@ -31,6 +31,7 @@ import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { attachButtonStyler } from 'vs/platform/theme/common/styler'; +import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput'; export class UserDataManualSyncViewPane extends TreeViewPane { @@ -189,31 +190,35 @@ export class UserDataManualSyncViewPane extends TreeViewPane { } async run(accessor: ServicesAccessor, handle: TreeViewItemHandleArg): Promise { const previewResource: IUserDataSyncResource = ManualSyncViewDataProvider.toUserDataSyncResourceGroup(handle.$treeItemHandle); - return that.showChanges(previewResource); + return that.open(previewResource); } })); } private async acceptLocal(userDataSyncResource: IUserDataSyncResource): Promise { - return this.withProgress(async () => { + await this.withProgress(async () => { const content = await this.userDataSyncService.resolveContent(userDataSyncResource.local); await this.userDataSyncPreview.accept(userDataSyncResource.syncResource, userDataSyncResource.local, content || ''); }); + await this.reopen(userDataSyncResource); } private async acceptRemote(userDataSyncResource: IUserDataSyncResource): Promise { - return this.withProgress(async () => { + await this.withProgress(async () => { const content = await this.userDataSyncService.resolveContent(userDataSyncResource.remote); await this.userDataSyncPreview.accept(userDataSyncResource.syncResource, userDataSyncResource.remote, content || ''); }); + await this.reopen(userDataSyncResource); } private async mergeResource(previewResource: IUserDataSyncResource): Promise { - return this.withProgress(() => this.userDataSyncPreview.merge(previewResource.preview)); + await this.withProgress(() => this.userDataSyncPreview.merge(previewResource.merged)); + await this.reopen(previewResource); } private async discardResource(previewResource: IUserDataSyncResource): Promise { - return this.withProgress(() => this.userDataSyncPreview.discard(previewResource.preview)); + this.close(previewResource); + return this.withProgress(() => this.userDataSyncPreview.discard(previewResource.merged)); } private async apply(): Promise { @@ -225,12 +230,12 @@ export class UserDataManualSyncViewPane extends TreeViewPane { await this.userDataSyncPreview.apply(); } - private async showChanges(previewResource: IUserDataSyncResource): Promise { - if (previewResource.localChange === Change.Added || previewResource.remoteChange === Change.Deleted) { - await this.editorService.openEditor({ resource: URI.revive(previewResource.remote), label: localize({ key: 'resourceLabel', comment: ['remote as in file in cloud'] }, "{0} (Remote)", basename(previewResource.remote)) }); + private async open(previewResource: IUserDataSyncResource): Promise { + if (previewResource.mergeState === MergeState.Accepted) { + await this.editorService.openEditor({ resource: previewResource.accepted, label: localize('preview', "{0} (Preview)", basename(previewResource.accepted)) }); } else { - const leftResource = URI.revive(previewResource.remote); - const rightResource = previewResource.mergeState === MergeState.Conflict ? URI.revive(previewResource.preview) : URI.revive(previewResource.local); + const leftResource = previewResource.remote; + const rightResource = previewResource.mergeState === MergeState.Conflict ? previewResource.merged : previewResource.local; const leftResourceName = localize({ key: 'leftResourceName', comment: ['remote as in file in cloud'] }, "{0} (Remote)", basename(leftResource)); const rightResourceName = localize({ key: 'rightResourceName', comment: ['local as in file in disk'] }, "{0} (Local)", basename(rightResource)); await this.editorService.openEditor({ @@ -245,6 +250,29 @@ export class UserDataManualSyncViewPane extends TreeViewPane { } } + private async reopen(previewResource: IUserDataSyncResource): Promise { + this.close(previewResource); + const resource = this.userDataSyncPreview.resources.find(({ local }) => isEqual(local, previewResource.local)); + if (resource) { + await this.open(resource); + } + } + + private close(previewResource: IUserDataSyncResource) { + for (const input of this.editorService.editors) { + if (input instanceof DiffEditorInput) { + // Close all diff editors + if (isEqual(previewResource.remote, input.secondary.resource)) { + input.dispose(); + } + } + // Close all preview editors + else if (isEqual(previewResource.accepted, input.resource)) { + input.dispose(); + } + } + } + private withProgress(task: () => Promise): Promise { return this.progressService.withProgress({ location: MANUAL_SYNC_VIEW_ID, delay: 500 }, task); } @@ -281,8 +309,9 @@ class ManualSyncViewDataProvider implements ITreeViewDataProvider { return { syncResource: parsed.syncResource, local: URI.revive(parsed.local), - preview: URI.revive(parsed.preview), remote: URI.revive(parsed.remote), + merged: URI.revive(parsed.merged), + accepted: URI.revive(parsed.accepted), localChange: parsed.localChange, remoteChange: parsed.remoteChange, mergeState: parsed.mergeState, diff --git a/src/vs/workbench/services/userDataSync/browser/userDataSyncWorkbenchService.ts b/src/vs/workbench/services/userDataSync/browser/userDataSyncWorkbenchService.ts index 5a5b57acdaa583a875f8435d79d53f63f25abb8e..820fc5ca881067a286d2d56a281eed649959549b 100644 --- a/src/vs/workbench/services/userDataSync/browser/userDataSyncWorkbenchService.ts +++ b/src/vs/workbench/services/userDataSync/browser/userDataSyncWorkbenchService.ts @@ -688,8 +688,8 @@ class UserDataSyncPreview extends Disposable implements IUserDataSyncPreview { private toUserDataSyncResourceGroups(syncResourcePreviews: [SyncResource, IResourcePreview[]][]): IUserDataSyncResource[] { return flatten( syncResourcePreviews.map(([syncResource, resourcePreviews]) => - resourcePreviews.map(({ localResource, remoteResource, previewResource, localChange, remoteChange, mergeState }) => - ({ syncResource, local: localResource, remote: remoteResource, preview: previewResource, localChange, remoteChange, mergeState }))) + resourcePreviews.map(({ localResource, remoteResource, previewResource, acceptedResource, localChange, remoteChange, mergeState }) => + ({ syncResource, local: localResource, remote: remoteResource, merged: previewResource, accepted: acceptedResource, localChange, remoteChange, mergeState }))) ); } diff --git a/src/vs/workbench/services/userDataSync/common/userDataSync.ts b/src/vs/workbench/services/userDataSync/common/userDataSync.ts index 194c0069863fd35bfaeb3897c437c90fa43005e3..1bac7f1a6c431ca3b37026112c4f60d200ab1e5a 100644 --- a/src/vs/workbench/services/userDataSync/common/userDataSync.ts +++ b/src/vs/workbench/services/userDataSync/common/userDataSync.ts @@ -33,7 +33,8 @@ export interface IUserDataSyncResource { readonly syncResource: SyncResource; readonly local: URI; readonly remote: URI; - readonly preview: URI; + readonly merged: URI; + readonly accepted: URI; readonly localChange: Change; readonly remoteChange: Change; readonly mergeState: MergeState; diff --git a/src/vs/workbench/services/userDataSync/electron-browser/userDataSyncService.ts b/src/vs/workbench/services/userDataSync/electron-browser/userDataSyncService.ts index 6e9ac9009cd9ef76e30d4e15e07659f244173a0e..dbe45d1ab02ea4d51b03965e63fba7a459ec2c26 100644 --- a/src/vs/workbench/services/userDataSync/electron-browser/userDataSyncService.ts +++ b/src/vs/workbench/services/userDataSync/electron-browser/userDataSyncService.ts @@ -233,6 +233,7 @@ class ManualSyncTask implements IManualSyncTask { localResource: URI.revive(r.localResource), remoteResource: URI.revive(r.remoteResource), previewResource: URI.revive(r.previewResource), + acceptedResource: URI.revive(r.acceptedResource), })) } ]));