提交 f14dd3f7 编写于 作者: S Sandeep Somavarapu

Fix #86681

上级 3ace4436
......@@ -50,10 +50,10 @@ import { IFileService } from 'vs/platform/files/common/files';
import { DiskFileSystemProvider } from 'vs/platform/files/electron-browser/diskFileSystemProvider';
import { Schemas } from 'vs/base/common/network';
import { IProductService } from 'vs/platform/product/common/productService';
import { IUserDataSyncService, IUserDataSyncStoreService, registerConfiguration, IUserDataSyncLogService, IUserDataSyncUtilService } from 'vs/platform/userDataSync/common/userDataSync';
import { IUserDataSyncService, IUserDataSyncStoreService, registerConfiguration, IUserDataSyncLogService, IUserDataSyncUtilService, ISettingsSyncService } from 'vs/platform/userDataSync/common/userDataSync';
import { UserDataSyncService } from 'vs/platform/userDataSync/common/userDataSyncService';
import { UserDataSyncStoreService } from 'vs/platform/userDataSync/common/userDataSyncStoreService';
import { UserDataSyncChannel, UserDataSyncUtilServiceClient } from 'vs/platform/userDataSync/common/userDataSyncIpc';
import { UserDataSyncChannel, UserDataSyncUtilServiceClient, SettingsSyncChannel } from 'vs/platform/userDataSync/common/userDataSyncIpc';
import { IElectronService } from 'vs/platform/electron/node/electron';
import { LoggerService } from 'vs/platform/log/node/loggerService';
import { UserDataSyncLogService } from 'vs/platform/userDataSync/common/userDataSyncLog';
......@@ -63,6 +63,7 @@ import { AuthTokenChannel } from 'vs/platform/auth/common/authTokenIpc';
import { ICredentialsService } from 'vs/platform/credentials/common/credentials';
import { KeytarCredentialsService } from 'vs/platform/credentials/node/credentialsService';
import { UserDataAutoSync } from 'vs/platform/userDataSync/electron-browser/userDataAutoSync';
import { SettingsSynchroniser } from 'vs/platform/userDataSync/common/settingsSync';
export interface ISharedProcessConfiguration {
readonly machineId: string;
......@@ -186,6 +187,7 @@ async function main(server: Server, initData: ISharedProcessInitData, configurat
services.set(IUserDataSyncLogService, new SyncDescriptor(UserDataSyncLogService));
services.set(IUserDataSyncUtilService, new UserDataSyncUtilServiceClient(server.getChannel('userDataSyncUtil', activeWindowRouter)));
services.set(IUserDataSyncStoreService, new SyncDescriptor(UserDataSyncStoreService));
services.set(ISettingsSyncService, new SyncDescriptor(SettingsSynchroniser));
services.set(IUserDataSyncService, new SyncDescriptor(UserDataSyncService));
registerConfiguration();
......@@ -209,6 +211,10 @@ async function main(server: Server, initData: ISharedProcessInitData, configurat
const authTokenChannel = new AuthTokenChannel(authTokenService);
server.registerChannel('authToken', authTokenChannel);
const settingsSyncService = accessor.get(ISettingsSyncService);
const settingsSyncChannel = new SettingsSyncChannel(settingsSyncService);
server.registerChannel('settingsSync', settingsSyncChannel);
const userDataSyncService = accessor.get(IUserDataSyncService);
const userDataSyncChannel = new UserDataSyncChannel(userDataSyncService);
server.registerChannel('userDataSync', userDataSyncChannel);
......
......@@ -10,6 +10,7 @@ import { values } from 'vs/base/common/map';
import { IStringDictionary } from 'vs/base/common/collections';
import { FormattingOptions } from 'vs/base/common/jsonFormatter';
import * as contentUtil from 'vs/platform/userDataSync/common/content';
import { IConflictSetting } from 'vs/platform/userDataSync/common/userDataSync';
export function computeRemoteContent(localContent: string, remoteContent: string, ignoredSettings: string[], formattingOptions: FormattingOptions): string {
if (ignoredSettings.length) {
......@@ -24,7 +25,7 @@ export function computeRemoteContent(localContent: string, remoteContent: string
return localContent;
}
export function merge(localContent: string, remoteContent: string, baseContent: string | null, ignoredSettings: string[], formattingOptions: FormattingOptions): { mergeContent: string, hasChanges: boolean, hasConflicts: boolean } {
export function merge(localContent: string, remoteContent: string, baseContent: string | null, ignoredSettings: string[], resolvedConflicts: { key: string, value: any | undefined }[], formattingOptions: FormattingOptions): { mergeContent: string, hasChanges: boolean, conflicts: IConflictSetting[] } {
const local = parse(localContent);
const remote = parse(remoteContent);
const base = baseContent ? parse(baseContent) : null;
......@@ -33,30 +34,41 @@ export function merge(localContent: string, remoteContent: string, baseContent:
const localToRemote = compare(local, remote, ignored);
if (localToRemote.added.size === 0 && localToRemote.removed.size === 0 && localToRemote.updated.size === 0) {
// No changes found between local and remote.
return { mergeContent: localContent, hasChanges: false, hasConflicts: false };
return { mergeContent: localContent, hasChanges: false, conflicts: [] };
}
const conflicts: Set<string> = new Set<string>();
const conflicts: Map<string, IConflictSetting> = new Map<string, IConflictSetting>();
const handledConflicts: Set<string> = new Set<string>();
const baseToLocal = base ? compare(base, local, ignored) : { added: Object.keys(local).reduce((r, k) => { r.add(k); return r; }, new Set<string>()), removed: new Set<string>(), updated: new Set<string>() };
const baseToRemote = base ? compare(base, remote, ignored) : { added: Object.keys(remote).reduce((r, k) => { r.add(k); return r; }, new Set<string>()), removed: new Set<string>(), updated: new Set<string>() };
let mergeContent = localContent;
const handleConflict = (conflictKey: string): void => {
handledConflicts.add(conflictKey);
const resolvedConflict = resolvedConflicts.filter(({ key }) => key === conflictKey)[0];
if (resolvedConflict) {
mergeContent = contentUtil.edit(mergeContent, [conflictKey], resolvedConflict.value, formattingOptions);
} else {
conflicts.set(conflictKey, { key: conflictKey, localValue: local[conflictKey], remoteValue: remote[conflictKey] });
}
};
// Removed settings in Local
for (const key of values(baseToLocal.removed)) {
// Got updated in remote
if (baseToRemote.updated.has(key)) {
conflicts.add(key);
handleConflict(key);
}
}
// Removed settings in Remote
for (const key of values(baseToRemote.removed)) {
if (conflicts.has(key)) {
if (handledConflicts.has(key)) {
continue;
}
// Got updated in local
if (baseToLocal.updated.has(key)) {
conflicts.add(key);
handleConflict(key);
} else {
mergeContent = contentUtil.edit(mergeContent, [key], undefined, formattingOptions);
}
......@@ -64,28 +76,28 @@ export function merge(localContent: string, remoteContent: string, baseContent:
// Added settings in Local
for (const key of values(baseToLocal.added)) {
if (conflicts.has(key)) {
if (handledConflicts.has(key)) {
continue;
}
// Got added in remote
if (baseToRemote.added.has(key)) {
// Has different value
if (localToRemote.updated.has(key)) {
conflicts.add(key);
handleConflict(key);
}
}
}
// Added settings in remote
for (const key of values(baseToRemote.added)) {
if (conflicts.has(key)) {
if (handledConflicts.has(key)) {
continue;
}
// Got added in local
if (baseToLocal.added.has(key)) {
// Has different value
if (localToRemote.updated.has(key)) {
conflicts.add(key);
handleConflict(key);
}
} else {
mergeContent = contentUtil.edit(mergeContent, [key], remote[key], formattingOptions);
......@@ -94,28 +106,28 @@ export function merge(localContent: string, remoteContent: string, baseContent:
// Updated settings in Local
for (const key of values(baseToLocal.updated)) {
if (conflicts.has(key)) {
if (handledConflicts.has(key)) {
continue;
}
// Got updated in remote
if (baseToRemote.updated.has(key)) {
// Has different value
if (localToRemote.updated.has(key)) {
conflicts.add(key);
handleConflict(key);
}
}
}
// Updated settings in Remote
for (const key of values(baseToRemote.updated)) {
if (conflicts.has(key)) {
if (handledConflicts.has(key)) {
continue;
}
// Got updated in local
if (baseToLocal.updated.has(key)) {
// Has different value
if (localToRemote.updated.has(key)) {
conflicts.add(key);
handleConflict(key);
}
} else {
mergeContent = contentUtil.edit(mergeContent, [key], remote[key], formattingOptions);
......@@ -126,7 +138,7 @@ export function merge(localContent: string, remoteContent: string, baseContent:
const conflictNodes: { key: string, node: Node | undefined }[] = [];
const tree = parseTree(mergeContent);
const eol = formattingOptions.eol!;
for (const key of values(conflicts)) {
for (const { key } of values(conflicts)) {
const node = findNodeAtLocation(tree, [key]);
conflictNodes.push({ key, node });
}
......@@ -166,7 +178,7 @@ export function merge(localContent: string, remoteContent: string, baseContent:
}
}
return { mergeContent, hasChanges: true, hasConflicts: conflicts.size > 0 };
return { mergeContent, hasChanges: true, conflicts: values(conflicts) };
}
function compare(from: IStringDictionary<any>, to: IStringDictionary<any>, ignored: Set<string>): { added: Set<string>, removed: Set<string>, updated: Set<string> } {
......
......@@ -5,7 +5,7 @@
import { Disposable } from 'vs/base/common/lifecycle';
import { IFileService, FileSystemProviderErrorCode, FileSystemProviderError, IFileContent } from 'vs/platform/files/common/files';
import { IUserData, UserDataSyncStoreError, UserDataSyncStoreErrorCode, ISynchroniser, SyncStatus, IUserDataSyncStoreService, DEFAULT_IGNORED_SETTINGS, IUserDataSyncLogService, IUserDataSyncUtilService } from 'vs/platform/userDataSync/common/userDataSync';
import { IUserData, UserDataSyncStoreError, UserDataSyncStoreErrorCode, SyncStatus, IUserDataSyncStoreService, DEFAULT_IGNORED_SETTINGS, IUserDataSyncLogService, IUserDataSyncUtilService, IConflictSetting, ISettingsSyncService } 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';
......@@ -25,10 +25,12 @@ interface ISyncPreviewResult {
readonly remoteUserData: IUserData;
readonly hasLocalChanged: boolean;
readonly hasRemoteChanged: boolean;
readonly hasConflicts: boolean;
readonly conflicts: IConflictSetting[];
}
export class SettingsSynchroniser extends Disposable implements ISynchroniser {
export class SettingsSynchroniser extends Disposable implements ISettingsSyncService {
_serviceBrand: any;
private static EXTERNAL_USER_DATA_SETTINGS_KEY: string = 'settings';
......@@ -81,12 +83,44 @@ export class SettingsSynchroniser extends Disposable implements ISynchroniser {
return false;
}
return this.doSync([]);
}
stop(): void {
if (this.syncPreviewResultPromise) {
this.syncPreviewResultPromise.cancel();
this.syncPreviewResultPromise = null;
this.logService.info('Settings: Stopped synchronizing settings.');
}
this.fileService.del(this.environmentService.settingsSyncPreviewResource);
this.setStatus(SyncStatus.Idle);
}
async getConflicts(): Promise<IConflictSetting[]> {
if (!this.syncPreviewResultPromise) {
return [];
}
if (this.status !== SyncStatus.HasConflicts) {
return [];
}
const preview = await this.syncPreviewResultPromise;
return preview.conflicts;
}
async resolveConflicts(resolvedConflicts: { key: string, value: any | undefined }[]): Promise<void> {
if (this.status === SyncStatus.HasConflicts) {
this.stop();
await this.doSync(resolvedConflicts);
}
}
private async doSync(resolvedConflicts: { key: string, value: any | undefined }[]): Promise<boolean> {
this.logService.trace('Settings: Started synchronizing settings...');
this.setStatus(SyncStatus.Syncing);
try {
const result = await this.getPreview();
if (result.hasConflicts) {
const result = await this.getPreview(resolvedConflicts);
if (result.conflicts.length) {
this.logService.info('Settings: Detected conflicts while synchronizing settings.');
this.setStatus(SyncStatus.HasConflicts);
return false;
......@@ -110,16 +144,6 @@ export class SettingsSynchroniser extends Disposable implements ISynchroniser {
}
}
stop(): void {
if (this.syncPreviewResultPromise) {
this.syncPreviewResultPromise.cancel();
this.syncPreviewResultPromise = null;
this.logService.info('Settings: Stopped synchronizing settings.');
}
this.fileService.del(this.environmentService.settingsSyncPreviewResource);
this.setStatus(SyncStatus.Idle);
}
private async continueSync(): Promise<boolean> {
if (this.status === SyncStatus.HasConflicts) {
await this.apply();
......@@ -178,14 +202,14 @@ export class SettingsSynchroniser extends Disposable implements ISynchroniser {
return parseErrors.length > 0;
}
private getPreview(): Promise<ISyncPreviewResult> {
private getPreview(resolvedConflicts: { key: string, value: any }[]): Promise<ISyncPreviewResult> {
if (!this.syncPreviewResultPromise) {
this.syncPreviewResultPromise = createCancelablePromise(token => this.generatePreview(token));
this.syncPreviewResultPromise = createCancelablePromise(token => this.generatePreview(resolvedConflicts, token));
}
return this.syncPreviewResultPromise;
}
private async generatePreview(token: CancellationToken): Promise<ISyncPreviewResult> {
private async generatePreview(resolvedConflicts: { key: string, value: any }[], token: CancellationToken): Promise<ISyncPreviewResult> {
const lastSyncData = await this.getLastSyncUserData();
const remoteUserData = await this.userDataSyncStoreService.read(SettingsSynchroniser.EXTERNAL_USER_DATA_SETTINGS_KEY, lastSyncData);
const remoteContent: string | null = remoteUserData.content;
......@@ -193,14 +217,14 @@ export class SettingsSynchroniser extends Disposable implements ISynchroniser {
const fileContent = await this.getLocalFileContent();
let hasLocalChanged: boolean = false;
let hasRemoteChanged: boolean = false;
let hasConflicts: boolean = false;
let conflicts: IConflictSetting[] = [];
let previewContent = null;
if (remoteContent) {
const localContent: string = fileContent ? fileContent.value.toString() : '{}';
if (this.hasErrors(localContent)) {
this.logService.error('Settings: Unable to sync settings as there are errors/warning in settings file.');
return { fileContent, remoteUserData, hasLocalChanged, hasRemoteChanged, hasConflicts };
return { fileContent, remoteUserData, hasLocalChanged, hasRemoteChanged, conflicts };
}
if (!lastSyncData // First time sync
......@@ -209,12 +233,12 @@ export class SettingsSynchroniser extends Disposable implements ISynchroniser {
) {
this.logService.trace('Settings: Merging remote settings with local settings...');
const formatUtils = await this.getFormattingOptions();
const result = merge(localContent, remoteContent, lastSyncData ? lastSyncData.content : null, this.getIgnoredSettings(), formatUtils);
const result = merge(localContent, remoteContent, lastSyncData ? lastSyncData.content : null, this.getIgnoredSettings(), resolvedConflicts, formatUtils);
// Sync only if there are changes
if (result.hasChanges) {
hasLocalChanged = result.mergeContent !== localContent;
hasRemoteChanged = result.mergeContent !== remoteContent;
hasConflicts = result.hasConflicts;
conflicts = result.conflicts;
previewContent = result.mergeContent;
}
}
......@@ -231,7 +255,7 @@ export class SettingsSynchroniser extends Disposable implements ISynchroniser {
await this.fileService.writeFile(this.environmentService.settingsSyncPreviewResource, VSBuffer.fromString(previewContent));
}
return { fileContent, remoteUserData, hasLocalChanged, hasRemoteChanged, hasConflicts };
return { fileContent, remoteUserData, hasLocalChanged, hasRemoteChanged, conflicts };
}
private _formattingOptions: Promise<FormattingOptions> | undefined = undefined;
......
......@@ -135,12 +135,9 @@ export function getUserDataSyncStore(configurationService: IConfigurationService
}
export const IUserDataSyncStoreService = createDecorator<IUserDataSyncStoreService>('IUserDataSyncStoreService');
export interface IUserDataSyncStoreService {
_serviceBrand: undefined;
readonly userDataSyncStore: IUserDataSyncStore | undefined;
read(key: string, oldValue: IUserData | null): Promise<IUserData>;
write(key: string, content: string, ref: string | null): Promise<string>;
}
......@@ -170,40 +167,41 @@ export const enum SyncStatus {
}
export interface ISynchroniser {
readonly status: SyncStatus;
readonly onDidChangeStatus: Event<SyncStatus>;
readonly onDidChangeLocal: Event<void>;
sync(_continue?: boolean): Promise<boolean>;
stop(): void;
}
export const IUserDataSyncService = createDecorator<IUserDataSyncService>('IUserDataSyncService');
export interface IUserDataSyncService extends ISynchroniser {
_serviceBrand: any;
readonly conflictsSource: SyncSource | null;
removeExtension(identifier: IExtensionIdentifier): Promise<void>;
}
export const IUserDataSyncUtilService = createDecorator<IUserDataSyncUtilService>('IUserDataSyncUtilService');
export interface IUserDataSyncUtilService {
_serviceBrand: undefined;
resolveUserBindings(userbindings: string[]): Promise<IStringDictionary<string>>;
resolveFormattingOptions(resource: URI): Promise<FormattingOptions>;
}
export const IUserDataSyncLogService = createDecorator<IUserDataSyncLogService>('IUserDataSyncLogService');
export interface IUserDataSyncLogService extends ILogService { }
export interface IUserDataSyncLogService extends ILogService {
export interface IConflictSetting {
key: string;
localValue: any | undefined;
remoteValue: any | undefined;
}
export const ISettingsSyncService = createDecorator<ISettingsSyncService>('ISettingsSyncService');
export interface ISettingsSyncService extends ISynchroniser {
_serviceBrand: any;
getConflicts(): Promise<IConflictSetting[]>;
resolveConflicts(resolvedConflicts: { key: string, value: any | undefined }[]): Promise<void>;
}
export const CONTEXT_SYNC_STATE = new RawContextKey<string>('syncStatus', SyncStatus.Uninitialized);
......@@ -5,7 +5,7 @@
import { IServerChannel, IChannel } from 'vs/base/parts/ipc/common/ipc';
import { Event } from 'vs/base/common/event';
import { IUserDataSyncService, IUserDataSyncUtilService } from 'vs/platform/userDataSync/common/userDataSync';
import { IUserDataSyncService, IUserDataSyncUtilService, ISettingsSyncService } from 'vs/platform/userDataSync/common/userDataSync';
import { URI } from 'vs/base/common/uri';
import { IStringDictionary } from 'vs/base/common/collections';
import { FormattingOptions } from 'vs/base/common/jsonFormatter';
......@@ -34,6 +34,30 @@ export class UserDataSyncChannel implements IServerChannel {
}
}
export class SettingsSyncChannel implements IServerChannel {
constructor(private readonly service: ISettingsSyncService) { }
listen(_: unknown, event: string): Event<any> {
switch (event) {
case 'onDidChangeStatus': return this.service.onDidChangeStatus;
case 'onDidChangeLocal': return this.service.onDidChangeLocal;
}
throw new Error(`Event not found: ${event}`);
}
call(context: any, command: string, args?: any): Promise<any> {
switch (command) {
case 'sync': return this.service.sync(args[0]);
case '_getInitialStatus': return Promise.resolve(this.service.status);
case 'stop': this.service.stop(); return Promise.resolve();
case 'getConflicts': return this.service.getConflicts();
case 'resolveConflicts': return this.service.resolveConflicts(args[0]);
}
throw new Error('Invalid call');
}
}
export class UserDataSycnUtilServiceChannel implements IServerChannel {
constructor(private readonly service: IUserDataSyncUtilService) { }
......
......@@ -3,7 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IUserDataSyncService, SyncStatus, ISynchroniser, IUserDataSyncStoreService, SyncSource } from 'vs/platform/userDataSync/common/userDataSync';
import { IUserDataSyncService, SyncStatus, ISynchroniser, IUserDataSyncStoreService, SyncSource, ISettingsSyncService } from 'vs/platform/userDataSync/common/userDataSync';
import { Disposable } from 'vs/base/common/lifecycle';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { SettingsSynchroniser } from 'vs/platform/userDataSync/common/settingsSync';
......@@ -30,7 +30,6 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
private _conflictsSource: SyncSource | null = null;
get conflictsSource(): SyncSource | null { return this._conflictsSource; }
private readonly settingsSynchroniser: SettingsSynchroniser;
private readonly keybindingsSynchroniser: KeybindingsSynchroniser;
private readonly extensionsSynchroniser: ExtensionsSynchroniser;
private readonly globalStateSynchroniser: GlobalStateSynchroniser;
......@@ -39,9 +38,9 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
@IUserDataSyncStoreService private readonly userDataSyncStoreService: IUserDataSyncStoreService,
@IInstantiationService private readonly instantiationService: IInstantiationService,
@IAuthTokenService private readonly authTokenService: IAuthTokenService,
@ISettingsSyncService private readonly settingsSynchroniser: ISettingsSyncService,
) {
super();
this.settingsSynchroniser = this._register(this.instantiationService.createInstance(SettingsSynchroniser));
this.keybindingsSynchroniser = this._register(this.instantiationService.createInstance(KeybindingsSynchroniser));
this.globalStateSynchroniser = this._register(this.instantiationService.createInstance(GlobalStateSynchroniser));
this.extensionsSynchroniser = this._register(this.instantiationService.createInstance(ExtensionsSynchroniser));
......
......@@ -5,6 +5,7 @@
import * as assert from 'assert';
import { merge, computeRemoteContent } from 'vs/platform/userDataSync/common/settingsMerge';
import { IConflictSetting } from 'vs/platform/userDataSync/common/userDataSync';
const formattingOptions = { eol: '\n', insertSpaces: false, tabSize: 4 };
......@@ -13,9 +14,9 @@ suite('SettingsMerge - No Conflicts', () => {
test('merge when local and remote are same with one entry', async () => {
const localContent = stringify({ 'a': 1 });
const remoteContent = stringify({ 'a': 1 });
const actual = merge(localContent, remoteContent, null, [], formattingOptions);
const actual = merge(localContent, remoteContent, null, [], [], formattingOptions);
assert.ok(!actual.hasChanges);
assert.ok(!actual.hasConflicts);
assert.equal(actual.conflicts.length, 0);
assert.equal(actual.mergeContent, localContent);
});
......@@ -28,9 +29,9 @@ suite('SettingsMerge - No Conflicts', () => {
'a': 1,
'b': 2
});
const actual = merge(localContent, remoteContent, null, [], formattingOptions);
const actual = merge(localContent, remoteContent, null, [], [], formattingOptions);
assert.ok(!actual.hasChanges);
assert.ok(!actual.hasConflicts);
assert.equal(actual.conflicts.length, 0);
assert.equal(actual.mergeContent, localContent);
});
......@@ -43,9 +44,9 @@ suite('SettingsMerge - No Conflicts', () => {
'a': 1,
'b': 2
});
const actual = merge(localContent, remoteContent, null, [], formattingOptions);
const actual = merge(localContent, remoteContent, null, [], [], formattingOptions);
assert.ok(!actual.hasChanges);
assert.ok(!actual.hasConflicts);
assert.equal(actual.conflicts.length, 0);
assert.equal(actual.mergeContent, localContent);
});
......@@ -62,9 +63,9 @@ suite('SettingsMerge - No Conflicts', () => {
'a': 1,
'b': 2
});
const actual = merge(localContent, remoteContent, baseContent, [], formattingOptions);
const actual = merge(localContent, remoteContent, baseContent, [], [], formattingOptions);
assert.ok(!actual.hasChanges);
assert.ok(!actual.hasConflicts);
assert.equal(actual.conflicts.length, 0);
assert.equal(actual.mergeContent, localContent);
});
......@@ -76,9 +77,9 @@ suite('SettingsMerge - No Conflicts', () => {
'a': 1,
'b': 2
});
const actual = merge(localContent, remoteContent, null, [], formattingOptions);
const actual = merge(localContent, remoteContent, null, [], [], formattingOptions);
assert.ok(actual.hasChanges);
assert.ok(!actual.hasConflicts);
assert.equal(actual.conflicts.length, 0);
assert.equal(actual.mergeContent, remoteContent);
});
......@@ -96,9 +97,9 @@ suite('SettingsMerge - No Conflicts', () => {
'b': 2,
'c': 3,
});
const actual = merge(localContent, remoteContent, null, [], formattingOptions);
const actual = merge(localContent, remoteContent, null, [], [], formattingOptions);
assert.ok(actual.hasChanges);
assert.ok(!actual.hasConflicts);
assert.equal(actual.conflicts.length, 0);
assert.equal(actual.mergeContent, expected);
});
......@@ -116,9 +117,9 @@ suite('SettingsMerge - No Conflicts', () => {
'b': 2,
'c': 3,
});
const actual = merge(localContent, remoteContent, localContent, [], formattingOptions);
const actual = merge(localContent, remoteContent, localContent, [], [], formattingOptions);
assert.ok(actual.hasChanges);
assert.ok(!actual.hasConflicts);
assert.equal(actual.conflicts.length, 0);
assert.equal(actual.mergeContent, expected);
});
......@@ -130,9 +131,9 @@ suite('SettingsMerge - No Conflicts', () => {
const remoteContent = stringify({
'a': 1,
});
const actual = merge(localContent, remoteContent, localContent, [], formattingOptions);
const actual = merge(localContent, remoteContent, localContent, [], [], formattingOptions);
assert.ok(actual.hasChanges);
assert.ok(!actual.hasConflicts);
assert.equal(actual.conflicts.length, 0);
assert.equal(actual.mergeContent, remoteContent);
});
......@@ -141,9 +142,9 @@ suite('SettingsMerge - No Conflicts', () => {
'a': 1,
});
const remoteContent = stringify({});
const actual = merge(localContent, remoteContent, localContent, [], formattingOptions);
const actual = merge(localContent, remoteContent, localContent, [], [], formattingOptions);
assert.ok(actual.hasChanges);
assert.ok(!actual.hasConflicts);
assert.equal(actual.conflicts.length, 0);
assert.deepEqual(JSON.parse(actual.mergeContent), {});
});
......@@ -154,9 +155,9 @@ suite('SettingsMerge - No Conflicts', () => {
const remoteContent = stringify({
'a': 2
});
const actual = merge(localContent, remoteContent, localContent, [], formattingOptions);
const actual = merge(localContent, remoteContent, localContent, [], [], formattingOptions);
assert.ok(actual.hasChanges);
assert.ok(!actual.hasConflicts);
assert.equal(actual.conflicts.length, 0);
assert.equal(actual.mergeContent, remoteContent);
});
......@@ -170,9 +171,9 @@ suite('SettingsMerge - No Conflicts', () => {
'c': 3,
'd': 4,
});
const actual = merge(localContent, remoteContent, localContent, [], formattingOptions);
const actual = merge(localContent, remoteContent, localContent, [], [], formattingOptions);
assert.ok(actual.hasChanges);
assert.ok(!actual.hasConflicts);
assert.equal(actual.conflicts.length, 0);
assert.equal(actual.mergeContent, remoteContent);
});
......@@ -186,9 +187,9 @@ suite('SettingsMerge - No Conflicts', () => {
const remoteContent = stringify({
'a': 1,
});
const actual = merge(localContent, remoteContent, null, [], formattingOptions);
const actual = merge(localContent, remoteContent, null, [], [], formattingOptions);
assert.ok(actual.hasChanges);
assert.ok(!actual.hasConflicts);
assert.equal(actual.conflicts.length, 0);
assert.equal(actual.mergeContent, localContent);
});
......@@ -202,9 +203,9 @@ suite('SettingsMerge - No Conflicts', () => {
const remoteContent = stringify({
'a': 1,
});
const actual = merge(localContent, remoteContent, remoteContent, [], formattingOptions);
const actual = merge(localContent, remoteContent, remoteContent, [], [], formattingOptions);
assert.ok(actual.hasChanges);
assert.ok(!actual.hasConflicts);
assert.equal(actual.conflicts.length, 0);
assert.equal(actual.mergeContent, localContent);
});
......@@ -219,9 +220,9 @@ suite('SettingsMerge - No Conflicts', () => {
'c': 3,
'd': 4,
});
const actual = merge(localContent, remoteContent, remoteContent, [], formattingOptions);
const actual = merge(localContent, remoteContent, remoteContent, [], [], formattingOptions);
assert.ok(actual.hasChanges);
assert.ok(!actual.hasConflicts);
assert.equal(actual.conflicts.length, 0);
assert.equal(actual.mergeContent, localContent);
});
......@@ -234,9 +235,9 @@ suite('SettingsMerge - No Conflicts', () => {
'a': 2,
'c': 2,
});
const actual = merge(localContent, remoteContent, remoteContent, [], formattingOptions);
const actual = merge(localContent, remoteContent, remoteContent, [], [], formattingOptions);
assert.ok(actual.hasChanges);
assert.ok(!actual.hasConflicts);
assert.equal(actual.conflicts.length, 0);
assert.equal(actual.mergeContent, localContent);
});
......@@ -250,9 +251,9 @@ suite('SettingsMerge - No Conflicts', () => {
const remoteContent = stringify({
'a': 1,
});
const actual = merge(localContent, remoteContent, remoteContent, [], formattingOptions);
const actual = merge(localContent, remoteContent, remoteContent, [], [], formattingOptions);
assert.ok(actual.hasChanges);
assert.ok(!actual.hasConflicts);
assert.equal(actual.conflicts.length, 0);
assert.equal(actual.mergeContent, localContent);
});
......@@ -267,9 +268,10 @@ suite('SettingsMerge - Conflicts', () => {
const remoteContent = stringify({
'a': 2
});
const actual = merge(localContent, remoteContent, null, [], formattingOptions);
const expectedConflicts: IConflictSetting[] = [{ key: 'a', localValue: 1, remoteValue: 2 }];
const actual = merge(localContent, remoteContent, null, [], [], formattingOptions);
assert.ok(actual.hasChanges);
assert.ok(actual.hasConflicts);
assert.deepEqual(actual.conflicts, expectedConflicts);
assert.equal(actual.mergeContent,
`{
<<<<<<< local
......@@ -290,9 +292,10 @@ suite('SettingsMerge - Conflicts', () => {
const remoteContent = stringify({
'b': 2
});
const actual = merge(localContent, remoteContent, baseContent, [], formattingOptions);
const expectedConflicts: IConflictSetting[] = [{ key: 'a', localValue: 2, remoteValue: undefined }];
const actual = merge(localContent, remoteContent, baseContent, [], [], formattingOptions);
assert.ok(actual.hasChanges);
assert.ok(actual.hasConflicts);
assert.deepEqual(actual.conflicts, expectedConflicts);
assert.equal(actual.mergeContent,
`{
<<<<<<< local
......@@ -311,9 +314,10 @@ suite('SettingsMerge - Conflicts', () => {
const remoteContent = stringify({
'a': 2
});
const actual = merge(localContent, remoteContent, baseContent, [], formattingOptions);
const expectedConflicts: IConflictSetting[] = [{ key: 'a', localValue: undefined, remoteValue: 2 }];
const actual = merge(localContent, remoteContent, baseContent, [], [], formattingOptions);
assert.ok(actual.hasChanges);
assert.ok(actual.hasConflicts);
assert.deepEqual(actual.conflicts, expectedConflicts);
assert.equal(actual.mergeContent,
`{
<<<<<<< local
......@@ -343,9 +347,15 @@ suite('SettingsMerge - Conflicts', () => {
'd': 6,
'e': 5,
});
const actual = merge(localContent, remoteContent, baseContent, [], formattingOptions);
const expectedConflicts: IConflictSetting[] = [
{ key: 'b', localValue: undefined, remoteValue: 3 },
{ key: 'a', localValue: 2, remoteValue: undefined },
{ key: 'e', localValue: 4, remoteValue: 5 },
{ key: 'd', localValue: 5, remoteValue: 6 },
];
const actual = merge(localContent, remoteContent, baseContent, [], [], formattingOptions);
assert.ok(actual.hasChanges);
assert.ok(actual.hasConflicts);
assert.deepEqual(actual.conflicts, expectedConflicts);
assert.equal(actual.mergeContent,
`{
<<<<<<< local
......@@ -371,6 +381,46 @@ suite('SettingsMerge - Conflicts', () => {
}`);
});
test('resolve when local and remote has moved forwareded with conflicts', async () => {
const baseContent = stringify({
'a': 1,
'b': 2,
'c': 3,
'd': 4,
});
const localContent = stringify({
'a': 2,
'c': 3,
'd': 5,
'e': 4,
'f': 1,
});
const remoteContent = stringify({
'b': 3,
'c': 3,
'd': 6,
'e': 5,
});
const expectedConflicts: IConflictSetting[] = [
{ key: 'd', localValue: 5, remoteValue: 6 },
];
const actual = merge(localContent, remoteContent, baseContent, [], [{ key: 'a', value: 2 }, { key: 'b', value: undefined }, { key: 'e', value: 5 }], formattingOptions);
assert.ok(actual.hasChanges);
assert.deepEqual(actual.conflicts, expectedConflicts);
assert.equal(actual.mergeContent,
`{
"a": 2,
"c": 3,
<<<<<<< local
"d": 5,
=======
"d": 6,
>>>>>>> remote
"e": 5,
"f": 1
}`);
});
});
suite('SettingsMerge - Ignored Settings', () => {
......@@ -378,9 +428,9 @@ suite('SettingsMerge - Ignored Settings', () => {
test('ignored setting is not merged when changed in local and remote', async () => {
const localContent = stringify({ 'a': 1 });
const remoteContent = stringify({ 'a': 2 });
const actual = merge(localContent, remoteContent, null, ['a'], formattingOptions);
const actual = merge(localContent, remoteContent, null, ['a'], [], formattingOptions);
assert.ok(!actual.hasChanges);
assert.ok(!actual.hasConflicts);
assert.equal(actual.conflicts.length, 0);
assert.equal(actual.mergeContent, localContent);
});
......@@ -388,45 +438,45 @@ suite('SettingsMerge - Ignored Settings', () => {
const baseContent = stringify({ 'a': 0 });
const localContent = stringify({ 'a': 1 });
const remoteContent = stringify({ 'a': 2 });
const actual = merge(localContent, remoteContent, baseContent, ['a'], formattingOptions);
const actual = merge(localContent, remoteContent, baseContent, ['a'], [], formattingOptions);
assert.ok(!actual.hasChanges);
assert.ok(!actual.hasConflicts);
assert.equal(actual.conflicts.length, 0);
assert.equal(actual.mergeContent, localContent);
});
test('ignored setting is not merged when added in remote', async () => {
const localContent = stringify({});
const remoteContent = stringify({ 'a': 1 });
const actual = merge(localContent, remoteContent, null, ['a'], formattingOptions);
const actual = merge(localContent, remoteContent, null, ['a'], [], formattingOptions);
assert.ok(!actual.hasChanges);
assert.ok(!actual.hasConflicts);
assert.equal(actual.conflicts.length, 0);
assert.equal(actual.mergeContent, localContent);
});
test('ignored setting is not merged when added in remote from base', async () => {
const localContent = stringify({ 'b': 2 });
const remoteContent = stringify({ 'a': 1, 'b': 2 });
const actual = merge(localContent, remoteContent, localContent, ['a'], formattingOptions);
const actual = merge(localContent, remoteContent, localContent, ['a'], [], formattingOptions);
assert.ok(!actual.hasChanges);
assert.ok(!actual.hasConflicts);
assert.equal(actual.conflicts.length, 0);
assert.equal(actual.mergeContent, localContent);
});
test('ignored setting is not merged when removed in remote', async () => {
const localContent = stringify({ 'a': 1 });
const remoteContent = stringify({});
const actual = merge(localContent, remoteContent, null, ['a'], formattingOptions);
const actual = merge(localContent, remoteContent, null, ['a'], [], formattingOptions);
assert.ok(!actual.hasChanges);
assert.ok(!actual.hasConflicts);
assert.equal(actual.conflicts.length, 0);
assert.equal(actual.mergeContent, localContent);
});
test('ignored setting is not merged when removed in remote from base', async () => {
const localContent = stringify({ 'a': 2 });
const remoteContent = stringify({});
const actual = merge(localContent, remoteContent, localContent, ['a'], formattingOptions);
const actual = merge(localContent, remoteContent, localContent, ['a'], [], formattingOptions);
assert.ok(!actual.hasChanges);
assert.ok(!actual.hasConflicts);
assert.equal(actual.conflicts.length, 0);
assert.equal(actual.mergeContent, localContent);
});
......@@ -453,9 +503,9 @@ suite('SettingsMerge - Ignored Settings', () => {
'a': 1,
'b': 3,
});
const actual = merge(localContent, remoteContent, baseContent, ['a', 'e'], formattingOptions);
const actual = merge(localContent, remoteContent, baseContent, ['a', 'e'], [], formattingOptions);
assert.ok(actual.hasChanges);
assert.ok(!actual.hasConflicts);
assert.equal(actual.conflicts.length, 0);
assert.equal(actual.mergeContent, expectedContent);
});
......@@ -478,12 +528,14 @@ suite('SettingsMerge - Ignored Settings', () => {
'b': 3,
'e': 6,
});
const actual = merge(localContent, remoteContent, baseContent, ['a', 'e'], formattingOptions);
//'{\n\t"a": 1,\n\n<<<<<<< local\t"b": 4,\n=======\n\t"b": 3,\n>>>>>>> remote'
//'{\n\t"a": 1,\n<<<<<<< local\n\t"b": 4,\n=======\n\t"b": 3,\n>>>>>>> remote\n<<<<<<< local\n\t"d": 5\n=======\n>>>>>>> remote\n}'
const expectedConflicts: IConflictSetting[] = [
{ key: 'd', localValue: 5, remoteValue: undefined },
{ key: 'b', localValue: 4, remoteValue: 3 },
];
const actual = merge(localContent, remoteContent, baseContent, ['a', 'e'], [], formattingOptions);
assert.ok(actual.hasChanges);
assert.ok(actual.hasChanges);
assert.ok(actual.hasConflicts);
assert.deepEqual(actual.conflicts, expectedConflicts);
assert.equal(actual.mergeContent,
`{
"a": 1,
......
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { SyncStatus, ISettingsSyncService, IConflictSetting } from 'vs/platform/userDataSync/common/userDataSync';
import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService';
import { Disposable } from 'vs/base/common/lifecycle';
import { Emitter, Event } from 'vs/base/common/event';
import { IChannel } from 'vs/base/parts/ipc/common/ipc';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
export class SettingsSyncService extends Disposable implements ISettingsSyncService {
_serviceBrand: undefined;
private readonly channel: IChannel;
private _status: SyncStatus = SyncStatus.Uninitialized;
get status(): SyncStatus { return this._status; }
private _onDidChangeStatus: Emitter<SyncStatus> = this._register(new Emitter<SyncStatus>());
readonly onDidChangeStatus: Event<SyncStatus> = this._onDidChangeStatus.event;
get onDidChangeLocal(): Event<void> { return this.channel.listen('onDidChangeLocal'); }
constructor(
@ISharedProcessService sharedProcessService: ISharedProcessService
) {
super();
this.channel = sharedProcessService.getChannel('settingsSync');
this.channel.call<SyncStatus>('_getInitialStatus').then(status => {
this.updateStatus(status);
this._register(this.channel.listen<SyncStatus>('onDidChangeStatus')(status => this.updateStatus(status)));
});
}
sync(_continue?: boolean): Promise<boolean> {
return this.channel.call('sync', [_continue]);
}
stop(): void {
this.channel.call('stop');
}
getConflicts(): Promise<IConflictSetting[]> {
return this.channel.call<IConflictSetting[]>('getConflicts');
}
resolveConflicts(conflicts: { key: string, value: any | undefined }[]): Promise<void> {
return this.channel.call('resolveConflicts', [conflicts]);
}
private async updateStatus(status: SyncStatus): Promise<void> {
this._status = status;
this._onDidChangeStatus.fire(status);
}
}
registerSingleton(ISettingsSyncService, SettingsSyncService);
......@@ -50,6 +50,7 @@ import 'vs/workbench/services/url/electron-browser/urlService';
import 'vs/workbench/services/workspaces/electron-browser/workspacesService';
import 'vs/workbench/services/workspaces/electron-browser/workspaceEditingService';
import 'vs/workbench/services/userDataSync/electron-browser/userDataSyncService';
import 'vs/workbench/services/userDataSync/electron-browser/settingsSyncService';
import 'vs/workbench/services/authToken/electron-browser/authTokenService';
import 'vs/workbench/services/host/electron-browser/desktopHostService';
import 'vs/workbench/services/request/electron-browser/requestService';
......
......@@ -65,10 +65,11 @@ import { ILoggerService } from 'vs/platform/log/common/log';
import { FileLoggerService } from 'vs/platform/log/common/fileLogService';
import { IAuthTokenService } from 'vs/platform/auth/common/auth';
import { AuthTokenService } from 'vs/workbench/services/authToken/browser/authTokenService';
import { IUserDataSyncStoreService, IUserDataSyncService, IUserDataSyncLogService } from 'vs/platform/userDataSync/common/userDataSync';
import { IUserDataSyncStoreService, IUserDataSyncService, IUserDataSyncLogService, ISettingsSyncService } from 'vs/platform/userDataSync/common/userDataSync';
import { UserDataSyncLogService } from 'vs/platform/userDataSync/common/userDataSyncLog';
import { UserDataSyncStoreService } from 'vs/platform/userDataSync/common/userDataSyncStoreService';
import { UserDataSyncService } from 'vs/platform/userDataSync/common/userDataSyncService';
import { SettingsSynchroniser } from 'vs/platform/userDataSync/common/settingsSync';
registerSingleton(IExtensionManagementService, ExtensionManagementService);
registerSingleton(IBackupFileService, BackupFileService);
......@@ -79,6 +80,7 @@ registerSingleton(ILoggerService, FileLoggerService);
registerSingleton(IAuthTokenService, AuthTokenService);
registerSingleton(IUserDataSyncLogService, UserDataSyncLogService);
registerSingleton(IUserDataSyncStoreService, UserDataSyncStoreService);
registerSingleton(ISettingsSyncService, SettingsSynchroniser);
registerSingleton(IUserDataSyncService, UserDataSyncService);
//#endregion
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册