Merge from vscode 718331d6f3ebd1b571530ab499edb266ddd493d5

This commit is contained in:
ADS Merger
2020-02-08 04:50:58 +00:00
parent 8c61538a27
commit 2af13c18d2
752 changed files with 16458 additions and 10063 deletions

View File

@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { IFileService, FileSystemProviderErrorCode, FileSystemProviderError, IFileContent, FileOperationError, FileOperationResult } from 'vs/platform/files/common/files';
import { IUserData, UserDataSyncError, UserDataSyncErrorCode, SyncStatus, IUserDataSyncStoreService, DEFAULT_IGNORED_SETTINGS, IUserDataSyncLogService, IUserDataSyncUtilService, IConflictSetting, ISettingsSyncService, CONFIGURATION_SYNC_STORE_KEY, SyncSource } from 'vs/platform/userDataSync/common/userDataSync';
import { IUserData, UserDataSyncError, UserDataSyncErrorCode, SyncStatus, IUserDataSyncStoreService, IUserDataSyncLogService, IUserDataSyncUtilService, IConflictSetting, ISettingsSyncService, CONFIGURATION_SYNC_STORE_KEY, SyncSource } from 'vs/platform/userDataSync/common/userDataSync';
import { VSBuffer } from 'vs/base/common/buffer';
import { parse, ParseError } from 'vs/base/common/json';
import { localize } from 'vs/nls';
@@ -12,22 +12,22 @@ import { Emitter, Event } from 'vs/base/common/event';
import { CancelablePromise, createCancelablePromise } from 'vs/base/common/async';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { startsWith } from 'vs/base/common/strings';
import { CancellationToken } from 'vs/base/common/cancellation';
import { updateIgnoredSettings, merge } from 'vs/platform/userDataSync/common/settingsMerge';
import { updateIgnoredSettings, merge, getIgnoredSettings } from 'vs/platform/userDataSync/common/settingsMerge';
import { FormattingOptions } from 'vs/base/common/jsonFormatter';
import * as arrays from 'vs/base/common/arrays';
import * as objects from 'vs/base/common/objects';
import { isEmptyObject } from 'vs/base/common/types';
import { isEmptyObject, isUndefinedOrNull } from 'vs/base/common/types';
import { edit } from 'vs/platform/userDataSync/common/content';
import { AbstractFileSynchroniser } from 'vs/platform/userDataSync/common/abstractSynchronizer';
interface ISyncPreviewResult {
readonly fileContent: IFileContent | null;
readonly remoteUserData: IUserData;
readonly lastSyncUserData: IUserData | null;
readonly content: string | null;
readonly hasLocalChanged: boolean;
readonly hasRemoteChanged: boolean;
readonly remoteContent: string | null;
readonly hasConflicts: boolean;
readonly conflictSettings: IConflictSetting[];
}
@@ -84,23 +84,23 @@ export class SettingsSynchroniser extends AbstractFileSynchroniser implements IS
this.logService.info('Settings: Started pulling settings...');
this.setStatus(SyncStatus.Syncing);
const remoteUserData = await this.getRemoteUserData();
const lastSyncUserData = await this.getLastSyncUserData();
const remoteUserData = await this.getRemoteUserData(lastSyncUserData);
if (remoteUserData.content !== null) {
const fileContent = await this.getLocalFileContent();
const formatUtils = await this.getFormattingOptions();
// Update ignored settings
// Update ignored settings from local file content
const content = updateIgnoredSettings(remoteUserData.content, fileContent ? fileContent.value.toString() : '{}', getIgnoredSettings(this.configurationService), formatUtils);
await this.fileService.writeFile(this.environmentService.settingsSyncPreviewResource, VSBuffer.fromString(content));
this.syncPreviewResultPromise = createCancelablePromise(() => Promise.resolve<ISyncPreviewResult>({
hasConflicts: false,
conflictSettings: [],
fileContent,
remoteUserData,
lastSyncUserData,
content,
hasLocalChanged: true,
hasRemoteChanged: false,
remoteContent: content,
remoteUserData,
hasConflicts: false,
conflictSettings: [],
}));
await this.apply();
@@ -135,20 +135,21 @@ export class SettingsSynchroniser extends AbstractFileSynchroniser implements IS
const formatUtils = await this.getFormattingOptions();
// Remove ignored settings
const content = updateIgnoredSettings(fileContent.value.toString(), '{}', getIgnoredSettings(this.configurationService), formatUtils);
await this.fileService.writeFile(this.environmentService.settingsSyncPreviewResource, VSBuffer.fromString(content));
const remoteUserData = await this.getRemoteUserData();
const lastSyncUserData = await this.getLastSyncUserData();
const remoteUserData = await this.getRemoteUserData(lastSyncUserData);
this.syncPreviewResultPromise = createCancelablePromise(() => Promise.resolve<ISyncPreviewResult>({
conflictSettings: [],
hasConflicts: false,
fileContent,
hasLocalChanged: false,
hasRemoteChanged: true,
remoteContent: content,
remoteUserData,
lastSyncUserData,
content,
hasRemoteChanged: true,
hasLocalChanged: false,
hasConflicts: false,
conflictSettings: [],
}));
await this.apply(undefined, true);
await this.apply(true);
}
// No local exists to push
@@ -182,7 +183,7 @@ export class SettingsSynchroniser extends AbstractFileSynchroniser implements IS
if (this.syncPreviewResultPromise) {
this.syncPreviewResultPromise.cancel();
this.syncPreviewResultPromise = null;
this.logService.info('Settings: Stopped synchronizing settings.');
this.logService.trace('Settings: Stopped synchronizing settings.');
}
await this.fileService.del(this.environmentService.settingsSyncPreviewResource);
this.setStatus(SyncStatus.Idle);
@@ -207,15 +208,21 @@ export class SettingsSynchroniser extends AbstractFileSynchroniser implements IS
return false;
}
async getRemoteContent(): Promise<string | null> {
async getRemoteContent(preview?: boolean): Promise<string | null> {
let content: string | null | undefined = null;
if (this.syncPreviewResultPromise) {
const preview = await this.syncPreviewResultPromise;
content = preview.remoteUserData?.content;
} else {
const remoteUserData = await this.getRemoteUserData();
const lastSyncData = await this.getLastSyncUserData();
const remoteUserData = await this.getRemoteUserData(lastSyncData);
content = remoteUserData.content;
}
if (preview && !isUndefinedOrNull(content)) {
const formatUtils = await this.getFormattingOptions();
// remove ignored settings from the remote content for preview
content = updateIgnoredSettings(content, '{}', getIgnoredSettings(this.configurationService), formatUtils);
}
return content !== undefined ? content : null;
}
@@ -227,16 +234,20 @@ export class SettingsSynchroniser extends AbstractFileSynchroniser implements IS
}
}
async resolveConflicts(content: string): Promise<void> {
async accept(content: string): Promise<void> {
if (this.status === SyncStatus.HasConflicts) {
try {
await this.apply(content, true);
const preview = await this.syncPreviewResultPromise!;
const formatUtils = await this.getFormattingOptions();
// Add ignored settings from local file content
content = updateIgnoredSettings(content, preview.fileContent ? preview.fileContent.value.toString() : '{}', getIgnoredSettings(this.configurationService), formatUtils);
this.syncPreviewResultPromise = createCancelablePromise(async () => ({ ...preview, content }));
await this.apply(true);
this.setStatus(SyncStatus.Idle);
} catch (e) {
this.logService.error(e);
if ((e instanceof FileSystemProviderError && e.code === FileSystemProviderErrorCode.FileExists) ||
(e instanceof FileOperationError && e.fileOperationResult === FileOperationResult.FILE_MODIFIED_SINCE)) {
throw new Error('New local version available.');
throw new UserDataSyncError('Failed to resolve conflicts as there is a new local version available.', UserDataSyncErrorCode.NewLocal);
}
throw e;
}
@@ -270,32 +281,27 @@ export class SettingsSynchroniser extends AbstractFileSynchroniser implements IS
this.setStatus(SyncStatus.Idle);
if (e instanceof UserDataSyncError && e.code === UserDataSyncErrorCode.Rejected) {
// Rejected as there is a new remote version. Syncing again,
this.logService.info('Settings: Failed to synchronise settings as there is a new remote version available. Synchronizing again...');
this.logService.info('Settings: Failed to synchronize settings as there is a new remote version available. Synchronizing again...');
return this.sync();
}
if ((e instanceof FileSystemProviderError && e.code === FileSystemProviderErrorCode.FileExists) ||
(e instanceof FileOperationError && e.fileOperationResult === FileOperationResult.FILE_MODIFIED_SINCE)) {
// Rejected as there is a new local version. Syncing again.
this.logService.info('Settings: Failed to synchronise settings as there is a new local version available. Synchronizing again...');
this.logService.info('Settings: Failed to synchronize settings as there is a new local version available. Synchronizing again...');
return this.sync();
}
throw e;
}
}
private async apply(content?: string, forcePush?: boolean): Promise<void> {
private async apply(forcePush?: boolean): Promise<void> {
if (!this.syncPreviewResultPromise) {
return;
}
if (content === undefined) {
if (await this.fileService.exists(this.environmentService.settingsSyncPreviewResource)) {
const settingsPreivew = await this.fileService.readFile(this.environmentService.settingsSyncPreviewResource);
content = settingsPreivew.value.toString();
}
}
let { fileContent, remoteUserData, lastSyncUserData, content, hasLocalChanged, hasRemoteChanged } = await this.syncPreviewResultPromise;
if (content !== undefined) {
if (content !== null) {
if (this.hasErrors(content)) {
const error = new Error(localize('errorInvalidSettings', "Unable to sync settings. Please resolve conflicts without any errors/warnings and try again."));
@@ -303,25 +309,20 @@ export class SettingsSynchroniser extends AbstractFileSynchroniser implements IS
throw error;
}
let { fileContent, remoteUserData, hasLocalChanged, hasRemoteChanged } = await this.syncPreviewResultPromise;
if (!hasLocalChanged && !hasRemoteChanged) {
this.logService.trace('Settings: No changes found during synchronizing settings.');
}
if (hasLocalChanged) {
this.logService.info('Settings: Updating local settings');
this.logService.trace('Settings: Updating local settings...');
await this.updateLocalFileContent(content, fileContent);
this.logService.info('Settings: Updated local settings');
}
if (hasRemoteChanged) {
const formatUtils = await this.getFormattingOptions();
const remoteContent = updateIgnoredSettings(content, remoteUserData.content || '{}', getIgnoredSettings(this.configurationService, content), formatUtils);
this.logService.info('Settings: Updating remote settings');
const ref = await this.updateRemoteUserData(remoteContent, forcePush ? null : remoteUserData.ref);
// Update ignored settings from remote
content = updateIgnoredSettings(content, remoteUserData.content || '{}', getIgnoredSettings(this.configurationService, content), formatUtils);
this.logService.trace('Settings: Updating remote settings...');
const ref = await this.updateRemoteUserData(content, forcePush ? null : remoteUserData.ref);
this.logService.info('Settings: Updated remote settings');
remoteUserData = { ref, content };
}
if (remoteUserData.content) {
this.logService.info('Settings: Updating last synchronised sttings');
await this.updateLastSyncUserData(remoteUserData);
}
// Delete the preview
await this.fileService.del(this.environmentService.settingsSyncPreviewResource);
@@ -329,6 +330,12 @@ export class SettingsSynchroniser extends AbstractFileSynchroniser implements IS
this.logService.trace('Settings: No changes found during synchronizing settings.');
}
if (lastSyncUserData?.ref !== remoteUserData.ref) {
this.logService.trace('Settings: Updating last synchronized settings...');
await this.updateLastSyncUserData(remoteUserData);
this.logService.info('Settings: Updated last synchronized settings');
}
this.syncPreviewResultPromise = null;
}
@@ -346,16 +353,17 @@ export class SettingsSynchroniser extends AbstractFileSynchroniser implements IS
}
private async generatePreview(resolvedConflicts: { key: string, value: any }[], token: CancellationToken): Promise<ISyncPreviewResult> {
const lastSyncData = await this.getLastSyncUserData();
const remoteUserData = await this.getRemoteUserData(lastSyncData);
const lastSyncUserData = await this.getLastSyncUserData();
const remoteUserData = await this.getRemoteUserData(lastSyncUserData);
// Get file content last to get the latest
const fileContent = await this.getLocalFileContent();
const formatUtils = await this.getFormattingOptions();
let content: string | null = null;
let hasLocalChanged: boolean = false;
let hasRemoteChanged: boolean = false;
let hasConflicts: boolean = false;
let conflictSettings: IConflictSetting[] = [];
let previewContent: string | null = null;
let remoteContent: string | null = null;
if (remoteUserData.content) {
const localContent: string = fileContent ? fileContent.value.toString() : '{}';
@@ -367,31 +375,30 @@ export class SettingsSynchroniser extends AbstractFileSynchroniser implements IS
else {
this.logService.trace('Settings: Merging remote settings with local settings...');
const formatUtils = await this.getFormattingOptions();
const result = merge(localContent, remoteUserData.content, lastSyncData ? lastSyncData.content : null, getIgnoredSettings(this.configurationService), resolvedConflicts, formatUtils);
hasConflicts = result.hasConflicts;
const result = merge(localContent, remoteUserData.content, lastSyncUserData ? lastSyncUserData.content : null, getIgnoredSettings(this.configurationService), resolvedConflicts, formatUtils);
content = result.localContent || result.remoteContent;
hasLocalChanged = result.localContent !== null;
hasRemoteChanged = result.remoteContent !== null;
hasConflicts = result.hasConflicts;
conflictSettings = result.conflictsSettings;
remoteContent = result.remoteContent;
previewContent = result.localContent || result.remoteContent;
}
}
// First time syncing to remote
else if (fileContent) {
this.logService.info('Settings: Remote settings does not exist. Synchronizing settings for the first time.');
this.logService.trace('Settings: Remote settings does not exist. Synchronizing settings for the first time.');
content = fileContent.value.toString();
hasRemoteChanged = true;
previewContent = fileContent.value.toString();
remoteContent = fileContent.value.toString();
}
if (previewContent && !token.isCancellationRequested) {
if (content && !token.isCancellationRequested) {
// Remove the ignored settings from the preview.
const previewContent = updateIgnoredSettings(content, '{}', getIgnoredSettings(this.configurationService), formatUtils);
await this.fileService.writeFile(this.environmentService.settingsSyncPreviewResource, VSBuffer.fromString(previewContent));
}
this.setConflicts(conflictSettings);
return { fileContent, remoteUserData, hasLocalChanged, hasRemoteChanged, remoteContent, conflictSettings, hasConflicts };
return { fileContent, remoteUserData, lastSyncUserData, content, hasLocalChanged, hasRemoteChanged, conflictSettings, hasConflicts };
}
private _formattingOptions: Promise<FormattingOptions> | undefined = undefined;
@@ -403,26 +410,3 @@ export class SettingsSynchroniser extends AbstractFileSynchroniser implements IS
}
}
export function getIgnoredSettings(configurationService: IConfigurationService, settingsContent?: string): string[] {
let value: string[] = [];
if (settingsContent) {
const setting = parse(settingsContent);
if (setting) {
value = setting['sync.ignoredSettings'];
}
} else {
value = configurationService.getValue<string[]>('sync.ignoredSettings');
}
const added: string[] = [], removed: string[] = [];
if (Array.isArray(value)) {
for (const key of value) {
if (startsWith(key, '-')) {
removed.push(key.substring(1));
} else {
added.push(key);
}
}
}
return [...DEFAULT_IGNORED_SETTINGS, ...added].filter(setting => removed.indexOf(setting) === -1);
}