mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 10:58:30 -05:00
Merge from vscode a4177f50c475fc0fa278a78235e3bee9ffdec781 (#8649)
* Merge from vscode a4177f50c475fc0fa278a78235e3bee9ffdec781 * distro * fix tests
This commit is contained in:
@@ -796,6 +796,7 @@ export interface IFilesConfiguration {
|
||||
eol: string;
|
||||
enableTrash: boolean;
|
||||
hotExit: string;
|
||||
preventSaveConflicts: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -484,15 +484,15 @@ export class DiskFileSystemProvider extends Disposable implements
|
||||
|
||||
//#region File Watching
|
||||
|
||||
private _onDidWatchErrorOccur: Emitter<string> = this._register(new Emitter<string>());
|
||||
readonly onDidErrorOccur: Event<string> = this._onDidWatchErrorOccur.event;
|
||||
private _onDidWatchErrorOccur = this._register(new Emitter<string>());
|
||||
readonly onDidErrorOccur = this._onDidWatchErrorOccur.event;
|
||||
|
||||
private _onDidChangeFile = this._register(new Emitter<readonly IFileChange[]>());
|
||||
get onDidChangeFile(): Event<readonly IFileChange[]> { return this._onDidChangeFile.event; }
|
||||
readonly onDidChangeFile = this._onDidChangeFile.event;
|
||||
|
||||
private recursiveWatcher: WindowsWatcherService | UnixWatcherService | NsfwWatcherService | undefined;
|
||||
private recursiveFoldersToWatch: { path: string, excludes: string[] }[] = [];
|
||||
private recursiveWatchRequestDelayer: ThrottledDelayer<void> = this._register(new ThrottledDelayer<void>(0));
|
||||
private recursiveWatchRequestDelayer = this._register(new ThrottledDelayer<void>(0));
|
||||
|
||||
private recursiveWatcherLogLevelListener: IDisposable | undefined;
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ suite('Files', () => {
|
||||
assert.strictEqual(true, r1.gotDeleted());
|
||||
});
|
||||
|
||||
function testIsEqual(testMethod: (pA: string | undefined, pB: string, ignoreCase: boolean) => boolean): void {
|
||||
function testIsEqual(testMethod: (pA: string, pB: string, ignoreCase: boolean) => boolean): void {
|
||||
|
||||
// corner cases
|
||||
assert(testMethod('', '', true));
|
||||
@@ -136,7 +136,7 @@ suite('Files', () => {
|
||||
test('isEqualOrParent (ignorecase)', function () {
|
||||
|
||||
// same assertions apply as with isEqual()
|
||||
testIsEqual(isEqualOrParent);
|
||||
testIsEqual(isEqualOrParent); //
|
||||
|
||||
if (isWindows) {
|
||||
assert(isEqualOrParent('c:\\some\\path', 'c:\\', true));
|
||||
@@ -182,4 +182,4 @@ suite('Files', () => {
|
||||
assert(!isEqualOrParent('foo/bar/test.ts', 'foo/BAR/test.', true));
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -22,7 +22,7 @@ import { IEditorOptions } from 'vs/platform/editor/common/editor';
|
||||
import { createDecorator, IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { attachListStyler, computeStyles, defaultListStyles, IColorMapping, attachStyler } from 'vs/platform/theme/common/styler';
|
||||
import { attachListStyler, computeStyles, defaultListStyles, IColorMapping } from 'vs/platform/theme/common/styler';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { InputFocusedContextKey } from 'vs/platform/contextkey/common/contextkeys';
|
||||
import { ObjectTree, IObjectTreeOptions, ICompressibleTreeRenderer, CompressibleObjectTree, ICompressibleObjectTreeOptions } from 'vs/base/browser/ui/tree/objectTree';
|
||||
@@ -287,7 +287,7 @@ export class WorkbenchList<T> extends List<T> {
|
||||
this.disposables.add((listService as ListService).register(this));
|
||||
|
||||
if (options.overrideStyles) {
|
||||
this.disposables.add(attachStyler(themeService, options.overrideStyles, this));
|
||||
this.disposables.add(attachListStyler(this, themeService, options.overrideStyles));
|
||||
}
|
||||
|
||||
this.disposables.add(this.onSelectionChange(() => {
|
||||
@@ -368,7 +368,7 @@ export class WorkbenchPagedList<T> extends PagedList<T> {
|
||||
this.disposables.add((listService as ListService).register(this));
|
||||
|
||||
if (options.overrideStyles) {
|
||||
this.disposables.add(attachStyler(themeService, options.overrideStyles, this));
|
||||
this.disposables.add(attachListStyler(this, themeService, options.overrideStyles));
|
||||
}
|
||||
|
||||
this.registerListeners();
|
||||
@@ -1044,7 +1044,7 @@ class WorkbenchTreeInternals<TInput, T, TFilterData> {
|
||||
this.disposables.push(
|
||||
this.contextKeyService,
|
||||
(listService as ListService).register(tree),
|
||||
overrideStyles ? attachStyler(themeService, overrideStyles, tree) : Disposable.None,
|
||||
overrideStyles ? attachListStyler(tree, themeService, overrideStyles) : Disposable.None,
|
||||
tree.onDidChangeSelection(() => {
|
||||
const selection = tree.getSelection();
|
||||
const focus = tree.getFocus();
|
||||
|
||||
@@ -102,7 +102,7 @@ export interface IProductConfiguration {
|
||||
|
||||
readonly portable?: string;
|
||||
|
||||
readonly extensionKind?: { readonly [extensionId: string]: ExtensionKind | ExtensionKind[]; };
|
||||
readonly extensionKind?: { readonly [extensionId: string]: ExtensionKind[]; };
|
||||
readonly extensionAllowedProposedApi?: readonly string[];
|
||||
|
||||
readonly msftInternalDomains?: string[];
|
||||
|
||||
@@ -11,8 +11,9 @@ export const ITunnelService = createDecorator<ITunnelService>('tunnelService');
|
||||
|
||||
export interface RemoteTunnel {
|
||||
readonly tunnelRemotePort: number;
|
||||
readonly tunnelRemoteHost: string;
|
||||
readonly tunnelLocalPort: number;
|
||||
readonly localAddress?: string;
|
||||
readonly localAddress: string;
|
||||
dispose(): void;
|
||||
}
|
||||
|
||||
|
||||
@@ -5,11 +5,19 @@
|
||||
|
||||
import { ISignService } from 'vs/platform/sign/common/sign';
|
||||
|
||||
/* tslint:disable */
|
||||
|
||||
declare module vsda {
|
||||
export class signer {
|
||||
sign(arg: any): any;
|
||||
}
|
||||
}
|
||||
|
||||
export class SignService implements ISignService {
|
||||
_serviceBrand: undefined;
|
||||
|
||||
private vsda(): Promise<typeof import('vsda')> {
|
||||
return import('vsda');
|
||||
private vsda(): Promise<typeof vsda> {
|
||||
return new Promise((resolve, reject) => require(['vsda'], resolve, reject));
|
||||
}
|
||||
|
||||
async sign(value: string): Promise<string> {
|
||||
@@ -20,9 +28,8 @@ export class SignService implements ISignService {
|
||||
return signer.sign(value);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('signer.sign: ' + e);
|
||||
// ignore errors silently
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,16 +11,16 @@ import { isUndefined, isUndefinedOrNull } from 'vs/base/common/types';
|
||||
import { IStateService } from 'vs/platform/state/node/state';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
|
||||
type StorageDatebase = { [key: string]: any; };
|
||||
type StorageDatabase = { [key: string]: any; };
|
||||
|
||||
export class FileStorage {
|
||||
|
||||
private _database: StorageDatebase | null = null;
|
||||
private _database: StorageDatabase | null = null;
|
||||
private lastFlushedSerializedDatabase: string | null = null;
|
||||
|
||||
constructor(private dbPath: string, private onError: (error: Error) => void) { }
|
||||
|
||||
private get database(): StorageDatebase {
|
||||
private get database(): StorageDatabase {
|
||||
if (!this._database) {
|
||||
this._database = this.loadSync();
|
||||
}
|
||||
@@ -42,7 +42,7 @@ export class FileStorage {
|
||||
this._database = database;
|
||||
}
|
||||
|
||||
private loadSync(): StorageDatebase {
|
||||
private loadSync(): StorageDatabase {
|
||||
try {
|
||||
this.lastFlushedSerializedDatabase = fs.readFileSync(this.dbPath).toString();
|
||||
|
||||
@@ -56,7 +56,7 @@ export class FileStorage {
|
||||
}
|
||||
}
|
||||
|
||||
private async loadAsync(): Promise<StorageDatebase> {
|
||||
private async loadAsync(): Promise<StorageDatabase> {
|
||||
try {
|
||||
this.lastFlushedSerializedDatabase = (await readFile(this.dbPath)).toString();
|
||||
|
||||
|
||||
169
src/vs/platform/userDataSync/common/extensionsMerge.ts
Normal file
169
src/vs/platform/userDataSync/common/extensionsMerge.ts
Normal file
@@ -0,0 +1,169 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { values, keys } from 'vs/base/common/map';
|
||||
import { ISyncExtension } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { IExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
|
||||
import { startsWith } from 'vs/base/common/strings';
|
||||
|
||||
export interface IMergeResult {
|
||||
added: ISyncExtension[];
|
||||
removed: IExtensionIdentifier[];
|
||||
updated: ISyncExtension[];
|
||||
remote: ISyncExtension[] | null;
|
||||
}
|
||||
|
||||
export function merge(localExtensions: ISyncExtension[], remoteExtensions: ISyncExtension[] | null, lastSyncExtensions: ISyncExtension[] | null, skippedExtensions: ISyncExtension[], ignoredExtensions: string[]): IMergeResult {
|
||||
const added: ISyncExtension[] = [];
|
||||
const removed: IExtensionIdentifier[] = [];
|
||||
const updated: ISyncExtension[] = [];
|
||||
|
||||
if (!remoteExtensions) {
|
||||
return {
|
||||
added,
|
||||
removed,
|
||||
updated,
|
||||
remote: localExtensions.filter(({ identifier }) => ignoredExtensions.every(id => id.toLowerCase() !== identifier.id.toLowerCase()))
|
||||
};
|
||||
}
|
||||
|
||||
const uuids: Map<string, string> = new Map<string, string>();
|
||||
const addUUID = (identifier: IExtensionIdentifier) => { if (identifier.uuid) { uuids.set(identifier.id.toLowerCase(), identifier.uuid); } };
|
||||
localExtensions.forEach(({ identifier }) => addUUID(identifier));
|
||||
remoteExtensions.forEach(({ identifier }) => addUUID(identifier));
|
||||
if (lastSyncExtensions) {
|
||||
lastSyncExtensions.forEach(({ identifier }) => addUUID(identifier));
|
||||
}
|
||||
|
||||
const addExtensionToMap = (map: Map<string, ISyncExtension>, extension: ISyncExtension) => {
|
||||
const uuid = extension.identifier.uuid || uuids.get(extension.identifier.id.toLowerCase());
|
||||
const key = uuid ? `uuid:${uuid}` : `id:${extension.identifier.id.toLowerCase()}`;
|
||||
map.set(key, extension);
|
||||
return map;
|
||||
};
|
||||
const localExtensionsMap = localExtensions.reduce(addExtensionToMap, new Map<string, ISyncExtension>());
|
||||
const remoteExtensionsMap = remoteExtensions.reduce(addExtensionToMap, new Map<string, ISyncExtension>());
|
||||
const newRemoteExtensionsMap = remoteExtensions.reduce(addExtensionToMap, new Map<string, ISyncExtension>());
|
||||
const lastSyncExtensionsMap = lastSyncExtensions ? lastSyncExtensions.reduce(addExtensionToMap, new Map<string, ISyncExtension>()) : null;
|
||||
const skippedExtensionsMap = skippedExtensions.reduce(addExtensionToMap, new Map<string, ISyncExtension>());
|
||||
const ignoredExtensionsSet = ignoredExtensions.reduce((set, id) => {
|
||||
const uuid = uuids.get(id.toLowerCase());
|
||||
return set.add(uuid ? `uuid:${uuid}` : `id:${id.toLowerCase()}`);
|
||||
}, new Set<string>());
|
||||
|
||||
const localToRemote = compare(localExtensionsMap, remoteExtensionsMap, ignoredExtensionsSet);
|
||||
if (localToRemote.added.size === 0 && localToRemote.removed.size === 0 && localToRemote.updated.size === 0) {
|
||||
// No changes found between local and remote.
|
||||
return { added: [], removed: [], updated: [], remote: null };
|
||||
}
|
||||
|
||||
const baseToLocal = compare(lastSyncExtensionsMap, localExtensionsMap, ignoredExtensionsSet);
|
||||
const baseToRemote = compare(lastSyncExtensionsMap, remoteExtensionsMap, ignoredExtensionsSet);
|
||||
|
||||
const massageSyncExtension = (extension: ISyncExtension, key: string): ISyncExtension => {
|
||||
const massagedExtension: ISyncExtension = {
|
||||
identifier: {
|
||||
id: extension.identifier.id,
|
||||
uuid: startsWith(key, 'uuid:') ? key.substring('uuid:'.length) : undefined
|
||||
},
|
||||
enabled: extension.enabled,
|
||||
};
|
||||
if (extension.version) {
|
||||
massagedExtension.version = extension.version;
|
||||
}
|
||||
return massagedExtension;
|
||||
};
|
||||
|
||||
// Remotely removed extension.
|
||||
for (const key of values(baseToRemote.removed)) {
|
||||
const e = localExtensionsMap.get(key);
|
||||
if (e) {
|
||||
removed.push(e.identifier);
|
||||
}
|
||||
}
|
||||
|
||||
// Remotely added extension
|
||||
for (const key of values(baseToRemote.added)) {
|
||||
// Got added in local
|
||||
if (baseToLocal.added.has(key)) {
|
||||
// Is different from local to remote
|
||||
if (localToRemote.updated.has(key)) {
|
||||
updated.push(massageSyncExtension(remoteExtensionsMap.get(key)!, key));
|
||||
}
|
||||
} else {
|
||||
// Add to local
|
||||
added.push(massageSyncExtension(remoteExtensionsMap.get(key)!, key));
|
||||
}
|
||||
}
|
||||
|
||||
// Remotely updated extensions
|
||||
for (const key of values(baseToRemote.updated)) {
|
||||
// If updated in local
|
||||
if (baseToLocal.updated.has(key)) {
|
||||
// Is different from local to remote
|
||||
if (localToRemote.updated.has(key)) {
|
||||
// update it in local
|
||||
updated.push(massageSyncExtension(remoteExtensionsMap.get(key)!, key));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Locally added extensions
|
||||
for (const key of values(baseToLocal.added)) {
|
||||
// Not there in remote
|
||||
if (!baseToRemote.added.has(key)) {
|
||||
newRemoteExtensionsMap.set(key, massageSyncExtension(localExtensionsMap.get(key)!, key));
|
||||
}
|
||||
}
|
||||
|
||||
// Locally updated extensions
|
||||
for (const key of values(baseToLocal.updated)) {
|
||||
// If removed in remote
|
||||
if (baseToRemote.removed.has(key)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// If not updated in remote
|
||||
if (!baseToRemote.updated.has(key)) {
|
||||
newRemoteExtensionsMap.set(key, massageSyncExtension(localExtensionsMap.get(key)!, key));
|
||||
}
|
||||
}
|
||||
|
||||
// Locally removed extensions
|
||||
for (const key of values(baseToLocal.removed)) {
|
||||
// If not skipped and not updated in remote
|
||||
if (!skippedExtensionsMap.has(key) && !baseToRemote.updated.has(key)) {
|
||||
newRemoteExtensionsMap.delete(key);
|
||||
}
|
||||
}
|
||||
|
||||
const remoteChanges = compare(remoteExtensionsMap, newRemoteExtensionsMap, new Set<string>());
|
||||
const remote = remoteChanges.added.size > 0 || remoteChanges.updated.size > 0 || remoteChanges.removed.size > 0 ? values(newRemoteExtensionsMap) : null;
|
||||
return { added, removed, updated, remote };
|
||||
}
|
||||
|
||||
function compare(from: Map<string, ISyncExtension> | null, to: Map<string, ISyncExtension>, ignoredExtensions: Set<string>): { added: Set<string>, removed: Set<string>, updated: Set<string> } {
|
||||
const fromKeys = from ? keys(from).filter(key => !ignoredExtensions.has(key)) : [];
|
||||
const toKeys = keys(to).filter(key => !ignoredExtensions.has(key));
|
||||
const added = toKeys.filter(key => fromKeys.indexOf(key) === -1).reduce((r, key) => { r.add(key); return r; }, new Set<string>());
|
||||
const removed = fromKeys.filter(key => toKeys.indexOf(key) === -1).reduce((r, key) => { r.add(key); return r; }, new Set<string>());
|
||||
const updated: Set<string> = new Set<string>();
|
||||
|
||||
for (const key of fromKeys) {
|
||||
if (removed.has(key)) {
|
||||
continue;
|
||||
}
|
||||
const fromExtension = from!.get(key)!;
|
||||
const toExtension = to.get(key);
|
||||
if (!toExtension
|
||||
|| fromExtension.enabled !== toExtension.enabled
|
||||
|| fromExtension.version !== toExtension.version
|
||||
) {
|
||||
updated.add(key);
|
||||
}
|
||||
}
|
||||
|
||||
return { added, removed, updated };
|
||||
}
|
||||
@@ -13,12 +13,11 @@ import { joinPath } from 'vs/base/common/resources';
|
||||
import { IExtensionManagementService, IExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
import { ExtensionType, IExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
|
||||
import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
|
||||
import { keys, values } from 'vs/base/common/map';
|
||||
import { startsWith } from 'vs/base/common/strings';
|
||||
import { IFileService } from 'vs/platform/files/common/files';
|
||||
import { Queue } from 'vs/base/common/async';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { localize } from 'vs/nls';
|
||||
import { merge } from 'vs/platform/userDataSync/common/extensionsMerge';
|
||||
|
||||
export interface ISyncPreviewResult {
|
||||
readonly added: ISyncExtension[];
|
||||
@@ -135,8 +134,14 @@ export class ExtensionsSynchroniser extends Disposable implements ISynchroniser
|
||||
|
||||
const localExtensions = await this.getLocalExtensions();
|
||||
|
||||
this.logService.trace('Extensions: Merging remote extensions with local extensions...');
|
||||
const { added, removed, updated, remote } = this.merge(localExtensions, remoteExtensions, lastSyncExtensions, skippedExtensions);
|
||||
if (remoteExtensions) {
|
||||
this.logService.trace('Extensions: Merging remote extensions with local extensions...');
|
||||
} else {
|
||||
this.logService.info('Extensions: Remote extensions does not exist. Synchronizing extensions for the first time.');
|
||||
}
|
||||
|
||||
const ignoredExtensions = this.configurationService.getValue<string[]>('sync.ignoredExtensions') || [];
|
||||
const { added, removed, updated, remote } = merge(localExtensions, remoteExtensions, lastSyncExtensions, skippedExtensions, ignoredExtensions);
|
||||
|
||||
if (!added.length && !removed.length && !updated.length && !remote) {
|
||||
this.logService.trace('Extensions: No changes found during synchronizing extensions.');
|
||||
@@ -162,160 +167,6 @@ export class ExtensionsSynchroniser extends Disposable implements ISynchroniser
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge Strategy:
|
||||
* - If remote does not exist, merge with local (First time sync)
|
||||
* - Overwrite local with remote changes. Removed, Added, Updated.
|
||||
* - Update remote with those local extension which are newly added or updated or removed and untouched in remote.
|
||||
*/
|
||||
private merge(localExtensions: ISyncExtension[], remoteExtensions: ISyncExtension[] | null, lastSyncExtensions: ISyncExtension[] | null, skippedExtensions: ISyncExtension[]): { added: ISyncExtension[], removed: IExtensionIdentifier[], updated: ISyncExtension[], remote: ISyncExtension[] | null } {
|
||||
const ignoredExtensions = this.configurationService.getValue<string[]>('sync.ignoredExtensions') || [];
|
||||
// First time sync
|
||||
if (!remoteExtensions) {
|
||||
this.logService.info('Extensions: Remote extensions does not exist. Synchronizing extensions for the first time.');
|
||||
return { added: [], removed: [], updated: [], remote: localExtensions.filter(({ identifier }) => ignoredExtensions.every(id => id.toLowerCase() !== identifier.id.toLowerCase())) };
|
||||
}
|
||||
|
||||
const uuids: Map<string, string> = new Map<string, string>();
|
||||
const addUUID = (identifier: IExtensionIdentifier) => { if (identifier.uuid) { uuids.set(identifier.id.toLowerCase(), identifier.uuid); } };
|
||||
localExtensions.forEach(({ identifier }) => addUUID(identifier));
|
||||
remoteExtensions.forEach(({ identifier }) => addUUID(identifier));
|
||||
if (lastSyncExtensions) {
|
||||
lastSyncExtensions.forEach(({ identifier }) => addUUID(identifier));
|
||||
}
|
||||
|
||||
const addExtensionToMap = (map: Map<string, ISyncExtension>, extension: ISyncExtension) => {
|
||||
const uuid = extension.identifier.uuid || uuids.get(extension.identifier.id.toLowerCase());
|
||||
const key = uuid ? `uuid:${uuid}` : `id:${extension.identifier.id.toLowerCase()}`;
|
||||
map.set(key, extension);
|
||||
return map;
|
||||
};
|
||||
const localExtensionsMap = localExtensions.reduce(addExtensionToMap, new Map<string, ISyncExtension>());
|
||||
const remoteExtensionsMap = remoteExtensions.reduce(addExtensionToMap, new Map<string, ISyncExtension>());
|
||||
const newRemoteExtensionsMap = remoteExtensions.reduce(addExtensionToMap, new Map<string, ISyncExtension>());
|
||||
const lastSyncExtensionsMap = lastSyncExtensions ? lastSyncExtensions.reduce(addExtensionToMap, new Map<string, ISyncExtension>()) : null;
|
||||
const skippedExtensionsMap = skippedExtensions.reduce(addExtensionToMap, new Map<string, ISyncExtension>());
|
||||
const ignoredExtensionsSet = ignoredExtensions.reduce((set, id) => {
|
||||
const uuid = uuids.get(id.toLowerCase());
|
||||
return set.add(uuid ? `uuid:${uuid}` : `id:${id.toLowerCase()}`);
|
||||
}, new Set<string>());
|
||||
|
||||
const localToRemote = this.compare(localExtensionsMap, remoteExtensionsMap, ignoredExtensionsSet);
|
||||
if (localToRemote.added.size === 0 && localToRemote.removed.size === 0 && localToRemote.updated.size === 0) {
|
||||
// No changes found between local and remote.
|
||||
return { added: [], removed: [], updated: [], remote: null };
|
||||
}
|
||||
|
||||
const added: ISyncExtension[] = [];
|
||||
const removed: IExtensionIdentifier[] = [];
|
||||
const updated: ISyncExtension[] = [];
|
||||
|
||||
const baseToLocal = lastSyncExtensionsMap ? this.compare(lastSyncExtensionsMap, localExtensionsMap, ignoredExtensionsSet) : { added: keys(localExtensionsMap).reduce((r, k) => { r.add(k); return r; }, new Set<string>()), removed: new Set<string>(), updated: new Set<string>() };
|
||||
const baseToRemote = lastSyncExtensionsMap ? this.compare(lastSyncExtensionsMap, remoteExtensionsMap, ignoredExtensionsSet) : { added: keys(remoteExtensionsMap).reduce((r, k) => { r.add(k); return r; }, new Set<string>()), removed: new Set<string>(), updated: new Set<string>() };
|
||||
|
||||
const massageSyncExtension = (extension: ISyncExtension, key: string): ISyncExtension => {
|
||||
return {
|
||||
identifier: {
|
||||
id: extension.identifier.id,
|
||||
uuid: startsWith(key, 'uuid:') ? key.substring('uuid:'.length) : undefined
|
||||
},
|
||||
enabled: extension.enabled,
|
||||
version: extension.version
|
||||
};
|
||||
};
|
||||
|
||||
// Remotely removed extension.
|
||||
for (const key of values(baseToRemote.removed)) {
|
||||
const e = localExtensionsMap.get(key);
|
||||
if (e) {
|
||||
removed.push(e.identifier);
|
||||
}
|
||||
}
|
||||
|
||||
// Remotely added extension
|
||||
for (const key of values(baseToRemote.added)) {
|
||||
// Got added in local
|
||||
if (baseToLocal.added.has(key)) {
|
||||
// Is different from local to remote
|
||||
if (localToRemote.updated.has(key)) {
|
||||
updated.push(massageSyncExtension(remoteExtensionsMap.get(key)!, key));
|
||||
}
|
||||
} else {
|
||||
// Add to local
|
||||
added.push(massageSyncExtension(remoteExtensionsMap.get(key)!, key));
|
||||
}
|
||||
}
|
||||
|
||||
// Remotely updated extensions
|
||||
for (const key of values(baseToRemote.updated)) {
|
||||
// If updated in local
|
||||
if (baseToLocal.updated.has(key)) {
|
||||
// Is different from local to remote
|
||||
if (localToRemote.updated.has(key)) {
|
||||
// update it in local
|
||||
updated.push(massageSyncExtension(remoteExtensionsMap.get(key)!, key));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Locally added extensions
|
||||
for (const key of values(baseToLocal.added)) {
|
||||
// Not there in remote
|
||||
if (!baseToRemote.added.has(key)) {
|
||||
newRemoteExtensionsMap.set(key, massageSyncExtension(localExtensionsMap.get(key)!, key));
|
||||
}
|
||||
}
|
||||
|
||||
// Locally updated extensions
|
||||
for (const key of values(baseToLocal.updated)) {
|
||||
// If removed in remote
|
||||
if (baseToRemote.removed.has(key)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// If not updated in remote
|
||||
if (!baseToRemote.updated.has(key)) {
|
||||
newRemoteExtensionsMap.set(key, massageSyncExtension(localExtensionsMap.get(key)!, key));
|
||||
}
|
||||
}
|
||||
|
||||
// Locally removed extensions
|
||||
for (const key of values(baseToLocal.removed)) {
|
||||
// If not skipped and not updated in remote
|
||||
if (!skippedExtensionsMap.has(key) && !baseToRemote.updated.has(key)) {
|
||||
newRemoteExtensionsMap.delete(key);
|
||||
}
|
||||
}
|
||||
|
||||
const remoteChanges = this.compare(remoteExtensionsMap, newRemoteExtensionsMap, new Set<string>());
|
||||
const remote = remoteChanges.added.size > 0 || remoteChanges.updated.size > 0 || remoteChanges.removed.size > 0 ? values(newRemoteExtensionsMap) : null;
|
||||
return { added, removed, updated, remote };
|
||||
}
|
||||
|
||||
private compare(from: Map<string, ISyncExtension>, to: Map<string, ISyncExtension>, ignoredExtensions: Set<string>): { added: Set<string>, removed: Set<string>, updated: Set<string> } {
|
||||
const fromKeys = keys(from).filter(key => !ignoredExtensions.has(key));
|
||||
const toKeys = keys(to).filter(key => !ignoredExtensions.has(key));
|
||||
const added = toKeys.filter(key => fromKeys.indexOf(key) === -1).reduce((r, key) => { r.add(key); return r; }, new Set<string>());
|
||||
const removed = fromKeys.filter(key => toKeys.indexOf(key) === -1).reduce((r, key) => { r.add(key); return r; }, new Set<string>());
|
||||
const updated: Set<string> = new Set<string>();
|
||||
|
||||
for (const key of fromKeys) {
|
||||
if (removed.has(key)) {
|
||||
continue;
|
||||
}
|
||||
const fromExtension = from.get(key)!;
|
||||
const toExtension = to.get(key);
|
||||
if (!toExtension
|
||||
|| fromExtension.enabled !== toExtension.enabled
|
||||
|| fromExtension.version !== toExtension.version
|
||||
) {
|
||||
updated.add(key);
|
||||
}
|
||||
}
|
||||
|
||||
return { added, removed, updated };
|
||||
}
|
||||
|
||||
private async updateLocalExtensions(added: ISyncExtension[], removed: IExtensionIdentifier[], updated: ISyncExtension[], skippedExtensions: ISyncExtension[]): Promise<ISyncExtension[]> {
|
||||
const removeFromSkipped: IExtensionIdentifier[] = [];
|
||||
const addToSkipped: ISyncExtension[] = [];
|
||||
|
||||
@@ -19,6 +19,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { OS, OperatingSystem } from 'vs/base/common/platform';
|
||||
import { isUndefined } from 'vs/base/common/types';
|
||||
import { FormattingOptions } from 'vs/base/common/jsonFormatter';
|
||||
|
||||
interface ISyncContent {
|
||||
mac?: string;
|
||||
@@ -217,7 +218,7 @@ export class KeybindingsSynchroniser extends Disposable implements ISynchroniser
|
||||
|| lastSyncContent !== remoteContent // Remote has forwarded
|
||||
) {
|
||||
this.logService.trace('Keybindings: Merging remote keybindings with local keybindings...');
|
||||
const formattingOptions = await this.userDataSyncUtilService.resolveFormattingOptions(this.environmentService.keybindingsResource);
|
||||
const formattingOptions = await this.getFormattingOptions();
|
||||
const result = await merge(localContent, remoteContent, lastSyncContent, formattingOptions, this.userDataSyncUtilService);
|
||||
// Sync only if there are changes
|
||||
if (result.hasChanges) {
|
||||
@@ -243,6 +244,14 @@ export class KeybindingsSynchroniser extends Disposable implements ISynchroniser
|
||||
return { fileContent, remoteUserData, hasLocalChanged, hasRemoteChanged, hasConflicts };
|
||||
}
|
||||
|
||||
private _formattingOptions: Promise<FormattingOptions> | undefined = undefined;
|
||||
private getFormattingOptions(): Promise<FormattingOptions> {
|
||||
if (!this._formattingOptions) {
|
||||
this._formattingOptions = this.userDataSyncUtilService.resolveFormattingOptions(this.environmentService.keybindingsResource);
|
||||
}
|
||||
return this._formattingOptions;
|
||||
}
|
||||
|
||||
private async getLocalContent(): Promise<IFileContent | null> {
|
||||
try {
|
||||
return await this.fileService.readFile(this.environmentService.keybindingsResource);
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IChannel, IServerChannel } from 'vs/base/parts/ipc/common/ipc';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { IUserDataSyncUtilService } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { IStringDictionary } from 'vs/base/common/collections';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { FormattingOptions } from 'vs/base/common/jsonFormatter';
|
||||
|
||||
export class UserDataSycnUtilServiceChannel implements IServerChannel {
|
||||
|
||||
constructor(private readonly service: IUserDataSyncUtilService) { }
|
||||
|
||||
listen(_: unknown, event: string): Event<any> {
|
||||
throw new Error(`Event not found: ${event}`);
|
||||
}
|
||||
|
||||
call(context: any, command: string, args?: any): Promise<any> {
|
||||
switch (command) {
|
||||
case 'resolveUserKeybindings': return this.service.resolveUserBindings(args[0]);
|
||||
case 'resolveFormattingOptions': return this.service.resolveFormattingOptions(URI.revive(args[0]));
|
||||
}
|
||||
throw new Error('Invalid call');
|
||||
}
|
||||
}
|
||||
|
||||
export class UserDataSyncUtilServiceClient implements IUserDataSyncUtilService {
|
||||
|
||||
_serviceBrand: undefined;
|
||||
|
||||
constructor(private readonly channel: IChannel) {
|
||||
}
|
||||
|
||||
async resolveUserBindings(userbindings: string[]): Promise<IStringDictionary<string>> {
|
||||
return this.channel.call('resolveUserKeybindings', [userbindings]);
|
||||
}
|
||||
|
||||
async resolveFormattingOptions(file: URI): Promise<FormattingOptions> {
|
||||
return this.channel.call('resolveFormattingOptions', [file]);
|
||||
}
|
||||
|
||||
}
|
||||
191
src/vs/platform/userDataSync/common/settingsMerge.ts
Normal file
191
src/vs/platform/userDataSync/common/settingsMerge.ts
Normal file
@@ -0,0 +1,191 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as objects from 'vs/base/common/objects';
|
||||
import { parse, findNodeAtLocation, parseTree, Node } from 'vs/base/common/json';
|
||||
import { setProperty } from 'vs/base/common/jsonEdit';
|
||||
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';
|
||||
|
||||
export function computeRemoteContent(localContent: string, remoteContent: string, ignoredSettings: string[], formattingOptions: FormattingOptions): string {
|
||||
if (ignoredSettings.length) {
|
||||
const remote = parse(remoteContent);
|
||||
const ignored = ignoredSettings.reduce((set, key) => { set.add(key); return set; }, new Set<string>());
|
||||
for (const key of ignoredSettings) {
|
||||
if (ignored.has(key)) {
|
||||
localContent = contentUtil.edit(localContent, [key], remote[key], formattingOptions);
|
||||
}
|
||||
}
|
||||
}
|
||||
return localContent;
|
||||
}
|
||||
|
||||
export function merge(localContent: string, remoteContent: string, baseContent: string | null, ignoredSettings: string[], formattingOptions: FormattingOptions): { mergeContent: string, hasChanges: boolean, hasConflicts: boolean } {
|
||||
const local = parse(localContent);
|
||||
const remote = parse(remoteContent);
|
||||
const base = baseContent ? parse(baseContent) : null;
|
||||
const ignored = ignoredSettings.reduce((set, key) => { set.add(key); return set; }, new Set<string>());
|
||||
|
||||
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 };
|
||||
}
|
||||
|
||||
const conflicts: 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;
|
||||
|
||||
// Removed settings in Local
|
||||
for (const key of values(baseToLocal.removed)) {
|
||||
// Got updated in remote
|
||||
if (baseToRemote.updated.has(key)) {
|
||||
conflicts.add(key);
|
||||
}
|
||||
}
|
||||
|
||||
// Removed settings in Remote
|
||||
for (const key of values(baseToRemote.removed)) {
|
||||
if (conflicts.has(key)) {
|
||||
continue;
|
||||
}
|
||||
// Got updated in local
|
||||
if (baseToLocal.updated.has(key)) {
|
||||
conflicts.add(key);
|
||||
} else {
|
||||
mergeContent = contentUtil.edit(mergeContent, [key], undefined, formattingOptions);
|
||||
}
|
||||
}
|
||||
|
||||
// Added settings in Local
|
||||
for (const key of values(baseToLocal.added)) {
|
||||
if (conflicts.has(key)) {
|
||||
continue;
|
||||
}
|
||||
// Got added in remote
|
||||
if (baseToRemote.added.has(key)) {
|
||||
// Has different value
|
||||
if (localToRemote.updated.has(key)) {
|
||||
conflicts.add(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Added settings in remote
|
||||
for (const key of values(baseToRemote.added)) {
|
||||
if (conflicts.has(key)) {
|
||||
continue;
|
||||
}
|
||||
// Got added in local
|
||||
if (baseToLocal.added.has(key)) {
|
||||
// Has different value
|
||||
if (localToRemote.updated.has(key)) {
|
||||
conflicts.add(key);
|
||||
}
|
||||
} else {
|
||||
mergeContent = contentUtil.edit(mergeContent, [key], remote[key], formattingOptions);
|
||||
}
|
||||
}
|
||||
|
||||
// Updated settings in Local
|
||||
for (const key of values(baseToLocal.updated)) {
|
||||
if (conflicts.has(key)) {
|
||||
continue;
|
||||
}
|
||||
// Got updated in remote
|
||||
if (baseToRemote.updated.has(key)) {
|
||||
// Has different value
|
||||
if (localToRemote.updated.has(key)) {
|
||||
conflicts.add(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Updated settings in Remote
|
||||
for (const key of values(baseToRemote.updated)) {
|
||||
if (conflicts.has(key)) {
|
||||
continue;
|
||||
}
|
||||
// Got updated in local
|
||||
if (baseToLocal.updated.has(key)) {
|
||||
// Has different value
|
||||
if (localToRemote.updated.has(key)) {
|
||||
conflicts.add(key);
|
||||
}
|
||||
} else {
|
||||
mergeContent = contentUtil.edit(mergeContent, [key], remote[key], formattingOptions);
|
||||
}
|
||||
}
|
||||
|
||||
if (conflicts.size > 0) {
|
||||
const conflictNodes: { key: string, node: Node | undefined }[] = [];
|
||||
const tree = parseTree(mergeContent);
|
||||
const eol = formattingOptions.eol!;
|
||||
for (const key of values(conflicts)) {
|
||||
const node = findNodeAtLocation(tree, [key]);
|
||||
conflictNodes.push({ key, node });
|
||||
}
|
||||
conflictNodes.sort((a, b) => {
|
||||
if (a.node && b.node) {
|
||||
return b.node.offset - a.node.offset;
|
||||
}
|
||||
return a.node ? 1 : -1;
|
||||
});
|
||||
const lastNode = tree.children ? tree.children[tree.children.length - 1] : undefined;
|
||||
for (const { key, node } of conflictNodes) {
|
||||
const remoteEdit = setProperty(`{${eol}\t${eol}}`, [key], remote[key], { tabSize: 4, insertSpaces: false, eol: eol })[0];
|
||||
const remoteContent = remoteEdit ? `${remoteEdit.content.substring(remoteEdit.offset + remoteEdit.length + 1)},${eol}` : '';
|
||||
if (node) {
|
||||
// Updated in Local and Remote with different value
|
||||
const localStartOffset = contentUtil.getLineStartOffset(mergeContent, eol, node.parent!.offset);
|
||||
const localEndOffset = contentUtil.getLineEndOffset(mergeContent, eol, node.offset + node.length);
|
||||
mergeContent = mergeContent.substring(0, localStartOffset)
|
||||
+ `<<<<<<< local${eol}`
|
||||
+ mergeContent.substring(localStartOffset, localEndOffset)
|
||||
+ `${eol}=======${eol}${remoteContent}>>>>>>> remote`
|
||||
+ mergeContent.substring(localEndOffset);
|
||||
} else {
|
||||
// Removed in Local, but updated in Remote
|
||||
if (lastNode) {
|
||||
const localStartOffset = contentUtil.getLineEndOffset(mergeContent, eol, lastNode.offset + lastNode.length);
|
||||
mergeContent = mergeContent.substring(0, localStartOffset)
|
||||
+ `${eol}<<<<<<< local${eol}=======${eol}${remoteContent}>>>>>>> remote`
|
||||
+ mergeContent.substring(localStartOffset);
|
||||
} else {
|
||||
const localStartOffset = tree.offset + 1;
|
||||
mergeContent = mergeContent.substring(0, localStartOffset)
|
||||
+ `${eol}<<<<<<< local${eol}=======${eol}${remoteContent}>>>>>>> remote${eol}`
|
||||
+ mergeContent.substring(localStartOffset);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { mergeContent, hasChanges: true, hasConflicts: conflicts.size > 0 };
|
||||
}
|
||||
|
||||
function compare(from: IStringDictionary<any>, to: IStringDictionary<any>, ignored: Set<string>): { added: Set<string>, removed: Set<string>, updated: Set<string> } {
|
||||
const fromKeys = Object.keys(from).filter(key => !ignored.has(key));
|
||||
const toKeys = Object.keys(to).filter(key => !ignored.has(key));
|
||||
const added = toKeys.filter(key => fromKeys.indexOf(key) === -1).reduce((r, key) => { r.add(key); return r; }, new Set<string>());
|
||||
const removed = fromKeys.filter(key => toKeys.indexOf(key) === -1).reduce((r, key) => { r.add(key); return r; }, new Set<string>());
|
||||
const updated: Set<string> = new Set<string>();
|
||||
|
||||
for (const key of fromKeys) {
|
||||
if (removed.has(key)) {
|
||||
continue;
|
||||
}
|
||||
const value1 = from[key];
|
||||
const value2 = to[key];
|
||||
if (!objects.equals(value1, value2)) {
|
||||
updated.add(key);
|
||||
}
|
||||
}
|
||||
|
||||
return { added, removed, updated };
|
||||
}
|
||||
@@ -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, ISettingsMergeService, IUserDataSyncStoreService, DEFAULT_IGNORED_SETTINGS, IUserDataSyncLogService } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { IUserData, UserDataSyncStoreError, UserDataSyncStoreErrorCode, ISynchroniser, SyncStatus, IUserDataSyncStoreService, DEFAULT_IGNORED_SETTINGS, IUserDataSyncLogService, IUserDataSyncUtilService } 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';
|
||||
@@ -17,6 +17,8 @@ import { joinPath, dirname } from 'vs/base/common/resources';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { startsWith } from 'vs/base/common/strings';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { computeRemoteContent, merge } from 'vs/platform/userDataSync/common/settingsMerge';
|
||||
import { FormattingOptions } from 'vs/base/common/jsonFormatter';
|
||||
|
||||
interface ISyncPreviewResult {
|
||||
readonly fileContent: IFileContent | null;
|
||||
@@ -46,8 +48,8 @@ export class SettingsSynchroniser extends Disposable implements ISynchroniser {
|
||||
@IFileService private readonly fileService: IFileService,
|
||||
@IEnvironmentService private readonly environmentService: IEnvironmentService,
|
||||
@IUserDataSyncStoreService private readonly userDataSyncStoreService: IUserDataSyncStoreService,
|
||||
@ISettingsMergeService private readonly settingsMergeService: ISettingsMergeService,
|
||||
@IUserDataSyncLogService private readonly logService: IUserDataSyncLogService,
|
||||
@IUserDataSyncUtilService private readonly userDataSyncUtilService: IUserDataSyncUtilService,
|
||||
@IConfigurationService private readonly configurationService: IConfigurationService,
|
||||
) {
|
||||
super();
|
||||
@@ -148,7 +150,8 @@ export class SettingsSynchroniser extends Disposable implements ISynchroniser {
|
||||
await this.writeToLocal(content, fileContent);
|
||||
}
|
||||
if (hasRemoteChanged) {
|
||||
const remoteContent = remoteUserData.content ? await this.settingsMergeService.computeRemoteContent(content, remoteUserData.content, this.getIgnoredSettings(content)) : content;
|
||||
const formatUtils = await this.getFormattingOptions();
|
||||
const remoteContent = remoteUserData.content ? computeRemoteContent(content, remoteUserData.content, this.getIgnoredSettings(content), formatUtils) : content;
|
||||
this.logService.info('Settings: Updating remote settings');
|
||||
const ref = await this.writeToRemote(remoteContent, remoteUserData.ref);
|
||||
remoteUserData = { ref, content };
|
||||
@@ -205,7 +208,8 @@ export class SettingsSynchroniser extends Disposable implements ISynchroniser {
|
||||
|| lastSyncData.content !== remoteContent // Remote has forwarded
|
||||
) {
|
||||
this.logService.trace('Settings: Merging remote settings with local settings...');
|
||||
const result = await this.settingsMergeService.merge(localContent, remoteContent, lastSyncData ? lastSyncData.content : null, this.getIgnoredSettings());
|
||||
const formatUtils = await this.getFormattingOptions();
|
||||
const result = merge(localContent, remoteContent, lastSyncData ? lastSyncData.content : null, this.getIgnoredSettings(), formatUtils);
|
||||
// Sync only if there are changes
|
||||
if (result.hasChanges) {
|
||||
hasLocalChanged = result.mergeContent !== localContent;
|
||||
@@ -230,6 +234,14 @@ export class SettingsSynchroniser extends Disposable implements ISynchroniser {
|
||||
return { fileContent, remoteUserData, hasLocalChanged, hasRemoteChanged, hasConflicts };
|
||||
}
|
||||
|
||||
private _formattingOptions: Promise<FormattingOptions> | undefined = undefined;
|
||||
private getFormattingOptions(): Promise<FormattingOptions> {
|
||||
if (!this._formattingOptions) {
|
||||
this._formattingOptions = this.userDataSyncUtilService.resolveFormattingOptions(this.environmentService.settingsResource);
|
||||
}
|
||||
return this._formattingOptions;
|
||||
}
|
||||
|
||||
private getIgnoredSettings(settingsContent?: string): string[] {
|
||||
let value: string[] = [];
|
||||
if (settingsContent) {
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IChannel, IServerChannel } from 'vs/base/parts/ipc/common/ipc';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { ISettingsMergeService } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
|
||||
export class SettingsMergeChannel implements IServerChannel {
|
||||
|
||||
constructor(private readonly service: ISettingsMergeService) { }
|
||||
|
||||
listen(_: unknown, event: string): Event<any> {
|
||||
throw new Error(`Event not found: ${event}`);
|
||||
}
|
||||
|
||||
call(context: any, command: string, args?: any): Promise<any> {
|
||||
switch (command) {
|
||||
case 'merge': return this.service.merge(args[0], args[1], args[2], args[3]);
|
||||
case 'computeRemoteContent': return this.service.computeRemoteContent(args[0], args[1], args[2]);
|
||||
}
|
||||
throw new Error('Invalid call');
|
||||
}
|
||||
}
|
||||
|
||||
export class SettingsMergeChannelClient implements ISettingsMergeService {
|
||||
|
||||
_serviceBrand: undefined;
|
||||
|
||||
constructor(private readonly channel: IChannel) {
|
||||
}
|
||||
|
||||
merge(localContent: string, remoteContent: string, baseContent: string | null, ignoredSettings: string[]): Promise<{ mergeContent: string, hasChanges: boolean, hasConflicts: boolean }> {
|
||||
return this.channel.call('merge', [localContent, remoteContent, baseContent, ignoredSettings]);
|
||||
}
|
||||
|
||||
computeRemoteContent(localContent: string, remoteContent: string, ignoredSettings: string[]): Promise<string> {
|
||||
return this.channel.call('computeRemoteContent', [localContent, remoteContent, ignoredSettings]);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -177,18 +177,6 @@ export interface IUserDataSyncService extends ISynchroniser {
|
||||
removeExtension(identifier: IExtensionIdentifier): Promise<void>;
|
||||
}
|
||||
|
||||
export const ISettingsMergeService = createDecorator<ISettingsMergeService>('ISettingsMergeService');
|
||||
|
||||
export interface ISettingsMergeService {
|
||||
|
||||
_serviceBrand: undefined;
|
||||
|
||||
merge(localContent: string, remoteContent: string, baseContent: string | null, ignoredSettings: string[]): Promise<{ mergeContent: string, hasChanges: boolean, hasConflicts: boolean }>;
|
||||
|
||||
computeRemoteContent(localContent: string, remoteContent: string, ignoredSettings: string[]): Promise<string>;
|
||||
|
||||
}
|
||||
|
||||
export const IUserDataSyncUtilService = createDecorator<IUserDataSyncUtilService>('IUserDataSyncUtilService');
|
||||
|
||||
export interface IUserDataSyncUtilService {
|
||||
|
||||
@@ -3,9 +3,12 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IServerChannel } from 'vs/base/parts/ipc/common/ipc';
|
||||
import { IServerChannel, IChannel } from 'vs/base/parts/ipc/common/ipc';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { IUserDataSyncService } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { IUserDataSyncService, IUserDataSyncUtilService } 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';
|
||||
|
||||
export class UserDataSyncChannel implements IServerChannel {
|
||||
|
||||
@@ -30,3 +33,38 @@ export class UserDataSyncChannel implements IServerChannel {
|
||||
throw new Error('Invalid call');
|
||||
}
|
||||
}
|
||||
|
||||
export class UserDataSycnUtilServiceChannel implements IServerChannel {
|
||||
|
||||
constructor(private readonly service: IUserDataSyncUtilService) { }
|
||||
|
||||
listen(_: unknown, event: string): Event<any> {
|
||||
throw new Error(`Event not found: ${event}`);
|
||||
}
|
||||
|
||||
call(context: any, command: string, args?: any): Promise<any> {
|
||||
switch (command) {
|
||||
case 'resolveUserKeybindings': return this.service.resolveUserBindings(args[0]);
|
||||
case 'resolveFormattingOptions': return this.service.resolveFormattingOptions(URI.revive(args[0]));
|
||||
}
|
||||
throw new Error('Invalid call');
|
||||
}
|
||||
}
|
||||
|
||||
export class UserDataSyncUtilServiceClient implements IUserDataSyncUtilService {
|
||||
|
||||
_serviceBrand: undefined;
|
||||
|
||||
constructor(private readonly channel: IChannel) {
|
||||
}
|
||||
|
||||
async resolveUserBindings(userbindings: string[]): Promise<IStringDictionary<string>> {
|
||||
return this.channel.call('resolveUserKeybindings', [userbindings]);
|
||||
}
|
||||
|
||||
async resolveFormattingOptions(file: URI): Promise<FormattingOptions> {
|
||||
return this.channel.call('resolveFormattingOptions', [file]);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
500
src/vs/platform/userDataSync/test/common/extensionsMerge.test.ts
Normal file
500
src/vs/platform/userDataSync/test/common/extensionsMerge.test.ts
Normal file
@@ -0,0 +1,500 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as assert from 'assert';
|
||||
import { ISyncExtension } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { merge } from 'vs/platform/userDataSync/common/extensionsMerge';
|
||||
|
||||
suite('ExtensionsMerge - No Conflicts', () => {
|
||||
|
||||
test('merge returns local extension if remote does not exist', async () => {
|
||||
const localExtensions: ISyncExtension[] = [
|
||||
{ identifier: { id: 'a', uuid: 'a' }, enabled: true },
|
||||
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
|
||||
{ identifier: { id: 'c', uuid: 'c' }, enabled: true },
|
||||
];
|
||||
|
||||
const actual = merge(localExtensions, null, null, [], []);
|
||||
|
||||
assert.deepEqual(actual.added, []);
|
||||
assert.deepEqual(actual.removed, []);
|
||||
assert.deepEqual(actual.updated, []);
|
||||
assert.deepEqual(actual.remote, localExtensions);
|
||||
});
|
||||
|
||||
test('merge returns local extension if remote does not exist with ignored extensions', async () => {
|
||||
const localExtensions: ISyncExtension[] = [
|
||||
{ identifier: { id: 'a', uuid: 'a' }, enabled: true },
|
||||
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
|
||||
{ identifier: { id: 'c', uuid: 'c' }, enabled: true },
|
||||
];
|
||||
const expected: ISyncExtension[] = [
|
||||
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
|
||||
{ identifier: { id: 'c', uuid: 'c' }, enabled: true },
|
||||
];
|
||||
|
||||
const actual = merge(localExtensions, null, null, [], ['a']);
|
||||
|
||||
assert.deepEqual(actual.added, []);
|
||||
assert.deepEqual(actual.removed, []);
|
||||
assert.deepEqual(actual.updated, []);
|
||||
assert.deepEqual(actual.remote, expected);
|
||||
});
|
||||
|
||||
test('merge returns local extension if remote does not exist with ignored extensions (ignore case)', async () => {
|
||||
const localExtensions: ISyncExtension[] = [
|
||||
{ identifier: { id: 'a', uuid: 'a' }, enabled: true },
|
||||
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
|
||||
{ identifier: { id: 'c', uuid: 'c' }, enabled: true },
|
||||
];
|
||||
const expected: ISyncExtension[] = [
|
||||
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
|
||||
{ identifier: { id: 'c', uuid: 'c' }, enabled: true },
|
||||
];
|
||||
|
||||
const actual = merge(localExtensions, null, null, [], ['A']);
|
||||
|
||||
assert.deepEqual(actual.added, []);
|
||||
assert.deepEqual(actual.removed, []);
|
||||
assert.deepEqual(actual.updated, []);
|
||||
assert.deepEqual(actual.remote, expected);
|
||||
});
|
||||
|
||||
test('merge returns local extension if remote does not exist with skipped extensions', async () => {
|
||||
const localExtensions: ISyncExtension[] = [
|
||||
{ identifier: { id: 'a', uuid: 'a' }, enabled: true },
|
||||
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
|
||||
{ identifier: { id: 'c', uuid: 'c' }, enabled: true },
|
||||
];
|
||||
const skippedExtension: ISyncExtension[] = [
|
||||
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
|
||||
];
|
||||
const expected: ISyncExtension[] = [
|
||||
{ identifier: { id: 'a', uuid: 'a' }, enabled: true },
|
||||
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
|
||||
{ identifier: { id: 'c', uuid: 'c' }, enabled: true },
|
||||
];
|
||||
|
||||
const actual = merge(localExtensions, null, null, skippedExtension, []);
|
||||
|
||||
assert.deepEqual(actual.added, []);
|
||||
assert.deepEqual(actual.removed, []);
|
||||
assert.deepEqual(actual.updated, []);
|
||||
assert.deepEqual(actual.remote, expected);
|
||||
});
|
||||
|
||||
test('merge returns local extension if remote does not exist with skipped and ignored extensions', async () => {
|
||||
const localExtensions: ISyncExtension[] = [
|
||||
{ identifier: { id: 'a', uuid: 'a' }, enabled: true },
|
||||
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
|
||||
{ identifier: { id: 'c', uuid: 'c' }, enabled: true },
|
||||
];
|
||||
const skippedExtension: ISyncExtension[] = [
|
||||
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
|
||||
];
|
||||
const expected: ISyncExtension[] = [
|
||||
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
|
||||
{ identifier: { id: 'c', uuid: 'c' }, enabled: true },
|
||||
];
|
||||
|
||||
const actual = merge(localExtensions, null, null, skippedExtension, ['a']);
|
||||
|
||||
assert.deepEqual(actual.added, []);
|
||||
assert.deepEqual(actual.removed, []);
|
||||
assert.deepEqual(actual.updated, []);
|
||||
assert.deepEqual(actual.remote, expected);
|
||||
});
|
||||
|
||||
test('merge local and remote extensions when there is no base', async () => {
|
||||
const localExtensions: ISyncExtension[] = [
|
||||
{ identifier: { id: 'a', uuid: 'a' }, enabled: true },
|
||||
{ identifier: { id: 'd', uuid: 'd' }, enabled: true },
|
||||
];
|
||||
const remoteExtensions: ISyncExtension[] = [
|
||||
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
|
||||
{ identifier: { id: 'c', uuid: 'c' }, enabled: true },
|
||||
];
|
||||
const expected: ISyncExtension[] = [
|
||||
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
|
||||
{ identifier: { id: 'c', uuid: 'c' }, enabled: true },
|
||||
{ identifier: { id: 'a', uuid: 'a' }, enabled: true },
|
||||
{ identifier: { id: 'd', uuid: 'd' }, enabled: true },
|
||||
];
|
||||
|
||||
const actual = merge(localExtensions, remoteExtensions, null, [], []);
|
||||
|
||||
assert.deepEqual(actual.added, [{ identifier: { id: 'b', uuid: 'b' }, enabled: true }, { identifier: { id: 'c', uuid: 'c' }, enabled: true }]);
|
||||
assert.deepEqual(actual.removed, []);
|
||||
assert.deepEqual(actual.updated, []);
|
||||
assert.deepEqual(actual.remote, expected);
|
||||
});
|
||||
|
||||
test('merge local and remote extensions when there is no base and with ignored extensions', async () => {
|
||||
const localExtensions: ISyncExtension[] = [
|
||||
{ identifier: { id: 'a', uuid: 'a' }, enabled: true },
|
||||
{ identifier: { id: 'd', uuid: 'd' }, enabled: true },
|
||||
];
|
||||
const remoteExtensions: ISyncExtension[] = [
|
||||
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
|
||||
{ identifier: { id: 'c', uuid: 'c' }, enabled: true },
|
||||
];
|
||||
const expected: ISyncExtension[] = [
|
||||
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
|
||||
{ identifier: { id: 'c', uuid: 'c' }, enabled: true },
|
||||
{ identifier: { id: 'd', uuid: 'd' }, enabled: true },
|
||||
];
|
||||
|
||||
const actual = merge(localExtensions, remoteExtensions, null, [], ['a']);
|
||||
|
||||
assert.deepEqual(actual.added, [{ identifier: { id: 'b', uuid: 'b' }, enabled: true }, { identifier: { id: 'c', uuid: 'c' }, enabled: true }]);
|
||||
assert.deepEqual(actual.removed, []);
|
||||
assert.deepEqual(actual.updated, []);
|
||||
assert.deepEqual(actual.remote, expected);
|
||||
});
|
||||
|
||||
test('merge local and remote extensions when remote is moved forwarded', async () => {
|
||||
const baseExtensions: ISyncExtension[] = [
|
||||
{ identifier: { id: 'a', uuid: 'a' }, enabled: true },
|
||||
{ identifier: { id: 'd', uuid: 'd' }, enabled: true },
|
||||
];
|
||||
const localExtensions: ISyncExtension[] = [
|
||||
{ identifier: { id: 'a', uuid: 'a' }, enabled: true },
|
||||
{ identifier: { id: 'd', uuid: 'd' }, enabled: true },
|
||||
];
|
||||
const remoteExtensions: ISyncExtension[] = [
|
||||
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
|
||||
{ identifier: { id: 'c', uuid: 'c' }, enabled: true },
|
||||
];
|
||||
|
||||
const actual = merge(localExtensions, remoteExtensions, baseExtensions, [], []);
|
||||
|
||||
assert.deepEqual(actual.added, [{ identifier: { id: 'b', uuid: 'b' }, enabled: true }, { identifier: { id: 'c', uuid: 'c' }, enabled: true }]);
|
||||
assert.deepEqual(actual.removed, [{ id: 'a', uuid: 'a' }, { id: 'd', uuid: 'd' }]);
|
||||
assert.deepEqual(actual.updated, []);
|
||||
assert.equal(actual.remote, null);
|
||||
});
|
||||
|
||||
test('merge local and remote extensions when remote moved forwarded with ignored extensions', async () => {
|
||||
const baseExtensions: ISyncExtension[] = [
|
||||
{ identifier: { id: 'a', uuid: 'a' }, enabled: true },
|
||||
{ identifier: { id: 'd', uuid: 'd' }, enabled: true },
|
||||
];
|
||||
const localExtensions: ISyncExtension[] = [
|
||||
{ identifier: { id: 'a', uuid: 'a' }, enabled: true },
|
||||
{ identifier: { id: 'd', uuid: 'd' }, enabled: true },
|
||||
];
|
||||
const remoteExtensions: ISyncExtension[] = [
|
||||
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
|
||||
{ identifier: { id: 'c', uuid: 'c' }, enabled: true },
|
||||
];
|
||||
|
||||
const actual = merge(localExtensions, remoteExtensions, baseExtensions, [], ['a']);
|
||||
|
||||
assert.deepEqual(actual.added, [{ identifier: { id: 'b', uuid: 'b' }, enabled: true }, { identifier: { id: 'c', uuid: 'c' }, enabled: true }]);
|
||||
assert.deepEqual(actual.removed, [{ id: 'd', uuid: 'd' }]);
|
||||
assert.deepEqual(actual.updated, []);
|
||||
assert.equal(actual.remote, null);
|
||||
});
|
||||
|
||||
test('merge local and remote extensions when remote is moved forwarded with skipped extensions', async () => {
|
||||
const baseExtensions: ISyncExtension[] = [
|
||||
{ identifier: { id: 'a', uuid: 'a' }, enabled: true },
|
||||
{ identifier: { id: 'd', uuid: 'd' }, enabled: true },
|
||||
];
|
||||
const localExtensions: ISyncExtension[] = [
|
||||
{ identifier: { id: 'd', uuid: 'd' }, enabled: true },
|
||||
];
|
||||
const skippedExtensions: ISyncExtension[] = [
|
||||
{ identifier: { id: 'a', uuid: 'a' }, enabled: true },
|
||||
];
|
||||
const remoteExtensions: ISyncExtension[] = [
|
||||
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
|
||||
{ identifier: { id: 'c', uuid: 'c' }, enabled: true },
|
||||
];
|
||||
|
||||
const actual = merge(localExtensions, remoteExtensions, baseExtensions, skippedExtensions, []);
|
||||
|
||||
assert.deepEqual(actual.added, [{ identifier: { id: 'b', uuid: 'b' }, enabled: true }, { identifier: { id: 'c', uuid: 'c' }, enabled: true }]);
|
||||
assert.deepEqual(actual.removed, [{ id: 'd', uuid: 'd' }]);
|
||||
assert.deepEqual(actual.updated, []);
|
||||
assert.equal(actual.remote, null);
|
||||
});
|
||||
|
||||
test('merge local and remote extensions when remote is moved forwarded with skipped and ignored extensions', async () => {
|
||||
const baseExtensions: ISyncExtension[] = [
|
||||
{ identifier: { id: 'a', uuid: 'a' }, enabled: true },
|
||||
{ identifier: { id: 'd', uuid: 'd' }, enabled: true },
|
||||
];
|
||||
const localExtensions: ISyncExtension[] = [
|
||||
{ identifier: { id: 'd', uuid: 'd' }, enabled: true },
|
||||
];
|
||||
const skippedExtensions: ISyncExtension[] = [
|
||||
{ identifier: { id: 'a', uuid: 'a' }, enabled: true },
|
||||
];
|
||||
const remoteExtensions: ISyncExtension[] = [
|
||||
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
|
||||
{ identifier: { id: 'c', uuid: 'c' }, enabled: true },
|
||||
];
|
||||
|
||||
const actual = merge(localExtensions, remoteExtensions, baseExtensions, skippedExtensions, ['b']);
|
||||
|
||||
assert.deepEqual(actual.added, [{ identifier: { id: 'c', uuid: 'c' }, enabled: true }]);
|
||||
assert.deepEqual(actual.removed, [{ id: 'd', uuid: 'd' }]);
|
||||
assert.deepEqual(actual.updated, []);
|
||||
assert.equal(actual.remote, null);
|
||||
});
|
||||
|
||||
test('merge local and remote extensions when local is moved forwarded', async () => {
|
||||
const baseExtensions: ISyncExtension[] = [
|
||||
{ identifier: { id: 'a', uuid: 'a' }, enabled: true },
|
||||
{ identifier: { id: 'd', uuid: 'd' }, enabled: true },
|
||||
];
|
||||
const localExtensions: ISyncExtension[] = [
|
||||
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
|
||||
{ identifier: { id: 'c', uuid: 'c' }, enabled: true },
|
||||
];
|
||||
const remoteExtensions: ISyncExtension[] = [
|
||||
{ identifier: { id: 'a', uuid: 'a' }, enabled: true },
|
||||
{ identifier: { id: 'd', uuid: 'd' }, enabled: true },
|
||||
];
|
||||
|
||||
const actual = merge(localExtensions, remoteExtensions, baseExtensions, [], []);
|
||||
|
||||
assert.deepEqual(actual.added, []);
|
||||
assert.deepEqual(actual.removed, []);
|
||||
assert.deepEqual(actual.updated, []);
|
||||
assert.deepEqual(actual.remote, localExtensions);
|
||||
});
|
||||
|
||||
test('merge local and remote extensions when local is moved forwarded with ignored settings', async () => {
|
||||
const baseExtensions: ISyncExtension[] = [
|
||||
{ identifier: { id: 'a', uuid: 'a' }, enabled: true },
|
||||
{ identifier: { id: 'd', uuid: 'd' }, enabled: true },
|
||||
];
|
||||
const localExtensions: ISyncExtension[] = [
|
||||
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
|
||||
{ identifier: { id: 'c', uuid: 'c' }, enabled: true },
|
||||
];
|
||||
const remoteExtensions: ISyncExtension[] = [
|
||||
{ identifier: { id: 'a', uuid: 'a' }, enabled: true },
|
||||
{ identifier: { id: 'd', uuid: 'd' }, enabled: true },
|
||||
];
|
||||
|
||||
const actual = merge(localExtensions, remoteExtensions, baseExtensions, [], ['b']);
|
||||
|
||||
assert.deepEqual(actual.added, []);
|
||||
assert.deepEqual(actual.removed, []);
|
||||
assert.deepEqual(actual.updated, []);
|
||||
assert.deepEqual(actual.remote, [
|
||||
{ identifier: { id: 'c', uuid: 'c' }, enabled: true },
|
||||
]);
|
||||
});
|
||||
|
||||
test('merge local and remote extensions when local is moved forwarded with skipped extensions', async () => {
|
||||
const baseExtensions: ISyncExtension[] = [
|
||||
{ identifier: { id: 'a', uuid: 'a' }, enabled: true },
|
||||
{ identifier: { id: 'd', uuid: 'd' }, enabled: true },
|
||||
];
|
||||
const skippedExtensions: ISyncExtension[] = [
|
||||
{ identifier: { id: 'd', uuid: 'd' }, enabled: true },
|
||||
];
|
||||
const localExtensions: ISyncExtension[] = [
|
||||
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
|
||||
{ identifier: { id: 'c', uuid: 'c' }, enabled: true },
|
||||
];
|
||||
const remoteExtensions: ISyncExtension[] = [
|
||||
{ identifier: { id: 'a', uuid: 'a' }, enabled: true },
|
||||
{ identifier: { id: 'd', uuid: 'd' }, enabled: true },
|
||||
];
|
||||
const expected: ISyncExtension[] = [
|
||||
{ identifier: { id: 'd', uuid: 'd' }, enabled: true },
|
||||
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
|
||||
{ identifier: { id: 'c', uuid: 'c' }, enabled: true },
|
||||
];
|
||||
|
||||
const actual = merge(localExtensions, remoteExtensions, baseExtensions, skippedExtensions, []);
|
||||
|
||||
assert.deepEqual(actual.added, []);
|
||||
assert.deepEqual(actual.removed, []);
|
||||
assert.deepEqual(actual.updated, []);
|
||||
assert.deepEqual(actual.remote, expected);
|
||||
});
|
||||
|
||||
test('merge local and remote extensions when local is moved forwarded with skipped and ignored extensions', async () => {
|
||||
const baseExtensions: ISyncExtension[] = [
|
||||
{ identifier: { id: 'a', uuid: 'a' }, enabled: true },
|
||||
{ identifier: { id: 'd', uuid: 'd' }, enabled: true },
|
||||
];
|
||||
const skippedExtensions: ISyncExtension[] = [
|
||||
{ identifier: { id: 'd', uuid: 'd' }, enabled: true },
|
||||
];
|
||||
const localExtensions: ISyncExtension[] = [
|
||||
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
|
||||
{ identifier: { id: 'c', uuid: 'c' }, enabled: true },
|
||||
];
|
||||
const remoteExtensions: ISyncExtension[] = [
|
||||
{ identifier: { id: 'a', uuid: 'a' }, enabled: true },
|
||||
{ identifier: { id: 'd', uuid: 'd' }, enabled: true },
|
||||
];
|
||||
const expected: ISyncExtension[] = [
|
||||
{ identifier: { id: 'd', uuid: 'd' }, enabled: true },
|
||||
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
|
||||
];
|
||||
|
||||
const actual = merge(localExtensions, remoteExtensions, baseExtensions, skippedExtensions, ['c']);
|
||||
|
||||
assert.deepEqual(actual.added, []);
|
||||
assert.deepEqual(actual.removed, []);
|
||||
assert.deepEqual(actual.updated, []);
|
||||
assert.deepEqual(actual.remote, expected);
|
||||
});
|
||||
|
||||
test('merge local and remote extensions when both moved forwarded', async () => {
|
||||
const baseExtensions: ISyncExtension[] = [
|
||||
{ identifier: { id: 'a', uuid: 'a' }, enabled: true },
|
||||
{ identifier: { id: 'd', uuid: 'd' }, enabled: true },
|
||||
];
|
||||
const localExtensions: ISyncExtension[] = [
|
||||
{ identifier: { id: 'a', uuid: 'a' }, enabled: true },
|
||||
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
|
||||
{ identifier: { id: 'c', uuid: 'c' }, enabled: true },
|
||||
];
|
||||
const remoteExtensions: ISyncExtension[] = [
|
||||
{ identifier: { id: 'd', uuid: 'd' }, enabled: true },
|
||||
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
|
||||
{ identifier: { id: 'e', uuid: 'e' }, enabled: true },
|
||||
];
|
||||
const expected: ISyncExtension[] = [
|
||||
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
|
||||
{ identifier: { id: 'e', uuid: 'e' }, enabled: true },
|
||||
{ identifier: { id: 'c', uuid: 'c' }, enabled: true },
|
||||
];
|
||||
|
||||
const actual = merge(localExtensions, remoteExtensions, baseExtensions, [], []);
|
||||
|
||||
assert.deepEqual(actual.added, [{ identifier: { id: 'e', uuid: 'e' }, enabled: true }]);
|
||||
assert.deepEqual(actual.removed, [{ id: 'a', uuid: 'a' }]);
|
||||
assert.deepEqual(actual.updated, []);
|
||||
assert.deepEqual(actual.remote, expected);
|
||||
});
|
||||
|
||||
test('merge local and remote extensions when both moved forwarded with ignored extensions', async () => {
|
||||
const baseExtensions: ISyncExtension[] = [
|
||||
{ identifier: { id: 'a', uuid: 'a' }, enabled: true },
|
||||
{ identifier: { id: 'd', uuid: 'd' }, enabled: true },
|
||||
];
|
||||
const localExtensions: ISyncExtension[] = [
|
||||
{ identifier: { id: 'a', uuid: 'a' }, enabled: true },
|
||||
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
|
||||
{ identifier: { id: 'c', uuid: 'c' }, enabled: true },
|
||||
];
|
||||
const remoteExtensions: ISyncExtension[] = [
|
||||
{ identifier: { id: 'd', uuid: 'd' }, enabled: true },
|
||||
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
|
||||
{ identifier: { id: 'e', uuid: 'e' }, enabled: true },
|
||||
];
|
||||
const expected: ISyncExtension[] = [
|
||||
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
|
||||
{ identifier: { id: 'e', uuid: 'e' }, enabled: true },
|
||||
{ identifier: { id: 'c', uuid: 'c' }, enabled: true },
|
||||
];
|
||||
|
||||
const actual = merge(localExtensions, remoteExtensions, baseExtensions, [], ['a', 'e']);
|
||||
|
||||
assert.deepEqual(actual.added, []);
|
||||
assert.deepEqual(actual.removed, []);
|
||||
assert.deepEqual(actual.updated, []);
|
||||
assert.deepEqual(actual.remote, expected);
|
||||
});
|
||||
|
||||
test('merge local and remote extensions when both moved forwarded with skipped extensions', async () => {
|
||||
const baseExtensions: ISyncExtension[] = [
|
||||
{ identifier: { id: 'a', uuid: 'a' }, enabled: true },
|
||||
{ identifier: { id: 'd', uuid: 'd' }, enabled: true },
|
||||
];
|
||||
const skippedExtensions: ISyncExtension[] = [
|
||||
{ identifier: { id: 'a', uuid: 'a' }, enabled: true },
|
||||
];
|
||||
const localExtensions: ISyncExtension[] = [
|
||||
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
|
||||
{ identifier: { id: 'c', uuid: 'c' }, enabled: true },
|
||||
];
|
||||
const remoteExtensions: ISyncExtension[] = [
|
||||
{ identifier: { id: 'd', uuid: 'd' }, enabled: true },
|
||||
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
|
||||
{ identifier: { id: 'e', uuid: 'e' }, enabled: true },
|
||||
];
|
||||
const expected: ISyncExtension[] = [
|
||||
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
|
||||
{ identifier: { id: 'e', uuid: 'e' }, enabled: true },
|
||||
{ identifier: { id: 'c', uuid: 'c' }, enabled: true },
|
||||
];
|
||||
|
||||
const actual = merge(localExtensions, remoteExtensions, baseExtensions, skippedExtensions, []);
|
||||
|
||||
assert.deepEqual(actual.added, [{ identifier: { id: 'e', uuid: 'e' }, enabled: true }]);
|
||||
assert.deepEqual(actual.removed, []);
|
||||
assert.deepEqual(actual.updated, []);
|
||||
assert.deepEqual(actual.remote, expected);
|
||||
});
|
||||
|
||||
test('merge local and remote extensions when both moved forwarded with skipped and ignoredextensions', async () => {
|
||||
const baseExtensions: ISyncExtension[] = [
|
||||
{ identifier: { id: 'a', uuid: 'a' }, enabled: true },
|
||||
{ identifier: { id: 'd', uuid: 'd' }, enabled: true },
|
||||
];
|
||||
const skippedExtensions: ISyncExtension[] = [
|
||||
{ identifier: { id: 'a', uuid: 'a' }, enabled: true },
|
||||
];
|
||||
const localExtensions: ISyncExtension[] = [
|
||||
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
|
||||
{ identifier: { id: 'c', uuid: 'c' }, enabled: true },
|
||||
];
|
||||
const remoteExtensions: ISyncExtension[] = [
|
||||
{ identifier: { id: 'd', uuid: 'd' }, enabled: true },
|
||||
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
|
||||
{ identifier: { id: 'e', uuid: 'e' }, enabled: true },
|
||||
];
|
||||
const expected: ISyncExtension[] = [
|
||||
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
|
||||
{ identifier: { id: 'e', uuid: 'e' }, enabled: true },
|
||||
{ identifier: { id: 'c', uuid: 'c' }, enabled: true },
|
||||
];
|
||||
|
||||
const actual = merge(localExtensions, remoteExtensions, baseExtensions, skippedExtensions, ['e']);
|
||||
|
||||
assert.deepEqual(actual.added, []);
|
||||
assert.deepEqual(actual.removed, []);
|
||||
assert.deepEqual(actual.updated, []);
|
||||
assert.deepEqual(actual.remote, expected);
|
||||
});
|
||||
|
||||
test('merge when remote extension has no uuid and different extension id case', async () => {
|
||||
const localExtensions: ISyncExtension[] = [
|
||||
{ identifier: { id: 'a', uuid: 'a' }, enabled: true },
|
||||
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
|
||||
{ identifier: { id: 'c', uuid: 'c' }, enabled: true },
|
||||
];
|
||||
const remoteExtensions: ISyncExtension[] = [
|
||||
{ identifier: { id: 'A' }, enabled: true },
|
||||
{ identifier: { id: 'd', uuid: 'd' }, enabled: true },
|
||||
];
|
||||
const expected: ISyncExtension[] = [
|
||||
{ identifier: { id: 'A' }, enabled: true },
|
||||
{ identifier: { id: 'd', uuid: 'd' }, enabled: true },
|
||||
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
|
||||
{ identifier: { id: 'c', uuid: 'c' }, enabled: true },
|
||||
];
|
||||
|
||||
const actual = merge(localExtensions, remoteExtensions, null, [], []);
|
||||
|
||||
assert.deepEqual(actual.added, [{ identifier: { id: 'd', uuid: 'd' }, enabled: true }]);
|
||||
assert.deepEqual(actual.removed, []);
|
||||
assert.deepEqual(actual.updated, []);
|
||||
assert.deepEqual(actual.remote, expected);
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
@@ -6,7 +6,6 @@
|
||||
import * as assert from 'assert';
|
||||
import { merge } from 'vs/platform/userDataSync/common/keybindingsMerge';
|
||||
import { IStringDictionary } from 'vs/base/common/collections';
|
||||
import { OperatingSystem, OS } from 'vs/base/common/platform';
|
||||
import { IUserDataSyncUtilService } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { FormattingOptions } from 'vs/base/common/jsonFormatter';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
@@ -441,7 +440,7 @@ suite('KeybindingsMerge - No Conflicts', () => {
|
||||
});
|
||||
|
||||
|
||||
suite.skip('KeybindingsMerge - Conflicts', () => {
|
||||
suite('KeybindingsMerge - Conflicts', () => {
|
||||
|
||||
test('merge when local and remote with one entry but different value', async () => {
|
||||
const localContent = stringify([{ key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }]);
|
||||
@@ -730,6 +729,6 @@ class MockUserDataSyncUtilService implements IUserDataSyncUtilService {
|
||||
}
|
||||
|
||||
async resolveFormattingOptions(file?: URI): Promise<FormattingOptions> {
|
||||
return { eol: OS === OperatingSystem.Windows ? '\r\n' : '\n', insertSpaces: false, tabSize: 4 };
|
||||
return { eol: '\n', insertSpaces: false, tabSize: 4 };
|
||||
}
|
||||
}
|
||||
|
||||
547
src/vs/platform/userDataSync/test/common/settingsMerge.test.ts
Normal file
547
src/vs/platform/userDataSync/test/common/settingsMerge.test.ts
Normal file
@@ -0,0 +1,547 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as assert from 'assert';
|
||||
import { merge, computeRemoteContent } from 'vs/platform/userDataSync/common/settingsMerge';
|
||||
|
||||
const formattingOptions = { eol: '\n', insertSpaces: false, tabSize: 4 };
|
||||
|
||||
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);
|
||||
assert.ok(!actual.hasChanges);
|
||||
assert.ok(!actual.hasConflicts);
|
||||
assert.equal(actual.mergeContent, localContent);
|
||||
});
|
||||
|
||||
test('merge when local and remote are same with multiple entries', async () => {
|
||||
const localContent = stringify({
|
||||
'a': 1,
|
||||
'b': 2
|
||||
});
|
||||
const remoteContent = stringify({
|
||||
'a': 1,
|
||||
'b': 2
|
||||
});
|
||||
const actual = merge(localContent, remoteContent, null, [], formattingOptions);
|
||||
assert.ok(!actual.hasChanges);
|
||||
assert.ok(!actual.hasConflicts);
|
||||
assert.equal(actual.mergeContent, localContent);
|
||||
});
|
||||
|
||||
test('merge when local and remote are same with multiple entries in different order', async () => {
|
||||
const localContent = stringify({
|
||||
'b': 2,
|
||||
'a': 1,
|
||||
});
|
||||
const remoteContent = stringify({
|
||||
'a': 1,
|
||||
'b': 2
|
||||
});
|
||||
const actual = merge(localContent, remoteContent, null, [], formattingOptions);
|
||||
assert.ok(!actual.hasChanges);
|
||||
assert.ok(!actual.hasConflicts);
|
||||
assert.equal(actual.mergeContent, localContent);
|
||||
});
|
||||
|
||||
test('merge when local and remote are same with different base content', async () => {
|
||||
const localContent = stringify({
|
||||
'b': 2,
|
||||
'a': 1,
|
||||
});
|
||||
const baseContent = stringify({
|
||||
'a': 2,
|
||||
'b': 1
|
||||
});
|
||||
const remoteContent = stringify({
|
||||
'a': 1,
|
||||
'b': 2
|
||||
});
|
||||
const actual = merge(localContent, remoteContent, baseContent, [], formattingOptions);
|
||||
assert.ok(!actual.hasChanges);
|
||||
assert.ok(!actual.hasConflicts);
|
||||
assert.equal(actual.mergeContent, localContent);
|
||||
});
|
||||
|
||||
test('merge when a new entry is added to remote', async () => {
|
||||
const localContent = stringify({
|
||||
'a': 1,
|
||||
});
|
||||
const remoteContent = stringify({
|
||||
'a': 1,
|
||||
'b': 2
|
||||
});
|
||||
const actual = merge(localContent, remoteContent, null, [], formattingOptions);
|
||||
assert.ok(actual.hasChanges);
|
||||
assert.ok(!actual.hasConflicts);
|
||||
assert.equal(actual.mergeContent, remoteContent);
|
||||
});
|
||||
|
||||
test('merge when multiple new entries are added to remote', async () => {
|
||||
const localContent = stringify({
|
||||
'a': 1,
|
||||
});
|
||||
const remoteContent = stringify({
|
||||
'b': 2,
|
||||
'a': 1,
|
||||
'c': 3,
|
||||
});
|
||||
const expected = stringify({
|
||||
'a': 1,
|
||||
'b': 2,
|
||||
'c': 3,
|
||||
});
|
||||
const actual = merge(localContent, remoteContent, null, [], formattingOptions);
|
||||
assert.ok(actual.hasChanges);
|
||||
assert.ok(!actual.hasConflicts);
|
||||
assert.equal(actual.mergeContent, expected);
|
||||
});
|
||||
|
||||
test('merge when multiple new entries are added to remote from base and local has not changed', async () => {
|
||||
const localContent = stringify({
|
||||
'a': 1,
|
||||
});
|
||||
const remoteContent = stringify({
|
||||
'b': 2,
|
||||
'a': 1,
|
||||
'c': 3,
|
||||
});
|
||||
const expected = stringify({
|
||||
'a': 1,
|
||||
'b': 2,
|
||||
'c': 3,
|
||||
});
|
||||
const actual = merge(localContent, remoteContent, localContent, [], formattingOptions);
|
||||
assert.ok(actual.hasChanges);
|
||||
assert.ok(!actual.hasConflicts);
|
||||
assert.equal(actual.mergeContent, expected);
|
||||
});
|
||||
|
||||
test('merge when an entry is removed from remote from base and local has not changed', async () => {
|
||||
const localContent = stringify({
|
||||
'a': 1,
|
||||
'b': 2,
|
||||
});
|
||||
const remoteContent = stringify({
|
||||
'a': 1,
|
||||
});
|
||||
const actual = merge(localContent, remoteContent, localContent, [], formattingOptions);
|
||||
assert.ok(actual.hasChanges);
|
||||
assert.ok(!actual.hasConflicts);
|
||||
assert.equal(actual.mergeContent, remoteContent);
|
||||
});
|
||||
|
||||
test('merge when all entries are removed from base and local has not changed', async () => {
|
||||
const localContent = stringify({
|
||||
'a': 1,
|
||||
});
|
||||
const remoteContent = stringify({});
|
||||
const actual = merge(localContent, remoteContent, localContent, [], formattingOptions);
|
||||
assert.ok(actual.hasChanges);
|
||||
assert.ok(!actual.hasConflicts);
|
||||
assert.deepEqual(JSON.parse(actual.mergeContent), {});
|
||||
});
|
||||
|
||||
test('merge when an entry is updated in remote from base and local has not changed', async () => {
|
||||
const localContent = stringify({
|
||||
'a': 1,
|
||||
});
|
||||
const remoteContent = stringify({
|
||||
'a': 2
|
||||
});
|
||||
const actual = merge(localContent, remoteContent, localContent, [], formattingOptions);
|
||||
assert.ok(actual.hasChanges);
|
||||
assert.ok(!actual.hasConflicts);
|
||||
assert.equal(actual.mergeContent, remoteContent);
|
||||
});
|
||||
|
||||
test('merge when remote has moved forwareded with multiple changes and local stays with base', async () => {
|
||||
const localContent = stringify({
|
||||
'a': 1,
|
||||
});
|
||||
const remoteContent = stringify({
|
||||
'a': 2,
|
||||
'b': 1,
|
||||
'c': 3,
|
||||
'd': 4,
|
||||
});
|
||||
const actual = merge(localContent, remoteContent, localContent, [], formattingOptions);
|
||||
assert.ok(actual.hasChanges);
|
||||
assert.ok(!actual.hasConflicts);
|
||||
assert.equal(actual.mergeContent, remoteContent);
|
||||
});
|
||||
|
||||
test('merge when a new entries are added to local', async () => {
|
||||
const localContent = stringify({
|
||||
'a': 1,
|
||||
'b': 2,
|
||||
'c': 3,
|
||||
'd': 4,
|
||||
});
|
||||
const remoteContent = stringify({
|
||||
'a': 1,
|
||||
});
|
||||
const actual = merge(localContent, remoteContent, null, [], formattingOptions);
|
||||
assert.ok(actual.hasChanges);
|
||||
assert.ok(!actual.hasConflicts);
|
||||
assert.equal(actual.mergeContent, localContent);
|
||||
});
|
||||
|
||||
test('merge when multiple new entries are added to local from base and remote is not changed', async () => {
|
||||
const localContent = stringify({
|
||||
'a': 2,
|
||||
'b': 1,
|
||||
'c': 3,
|
||||
'd': 4,
|
||||
});
|
||||
const remoteContent = stringify({
|
||||
'a': 1,
|
||||
});
|
||||
const actual = merge(localContent, remoteContent, remoteContent, [], formattingOptions);
|
||||
assert.ok(actual.hasChanges);
|
||||
assert.ok(!actual.hasConflicts);
|
||||
assert.equal(actual.mergeContent, localContent);
|
||||
});
|
||||
|
||||
test('merge when an entry is removed from local from base and remote has not changed', async () => {
|
||||
const localContent = stringify({
|
||||
'a': 1,
|
||||
'c': 2
|
||||
});
|
||||
const remoteContent = stringify({
|
||||
'a': 2,
|
||||
'b': 1,
|
||||
'c': 3,
|
||||
'd': 4,
|
||||
});
|
||||
const actual = merge(localContent, remoteContent, remoteContent, [], formattingOptions);
|
||||
assert.ok(actual.hasChanges);
|
||||
assert.ok(!actual.hasConflicts);
|
||||
assert.equal(actual.mergeContent, localContent);
|
||||
});
|
||||
|
||||
test('merge when an entry is updated in local from base and remote has not changed', async () => {
|
||||
const localContent = stringify({
|
||||
'a': 1,
|
||||
'c': 2
|
||||
});
|
||||
const remoteContent = stringify({
|
||||
'a': 2,
|
||||
'c': 2,
|
||||
});
|
||||
const actual = merge(localContent, remoteContent, remoteContent, [], formattingOptions);
|
||||
assert.ok(actual.hasChanges);
|
||||
assert.ok(!actual.hasConflicts);
|
||||
assert.equal(actual.mergeContent, localContent);
|
||||
});
|
||||
|
||||
test('merge when local has moved forwarded with multiple changes and remote stays with base', async () => {
|
||||
const localContent = stringify({
|
||||
'a': 2,
|
||||
'b': 1,
|
||||
'c': 3,
|
||||
'd': 4,
|
||||
});
|
||||
const remoteContent = stringify({
|
||||
'a': 1,
|
||||
});
|
||||
const actual = merge(localContent, remoteContent, remoteContent, [], formattingOptions);
|
||||
assert.ok(actual.hasChanges);
|
||||
assert.ok(!actual.hasConflicts);
|
||||
assert.equal(actual.mergeContent, localContent);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
suite('SettingsMerge - Conflicts', () => {
|
||||
|
||||
test('merge when local and remote with one entry but different value', async () => {
|
||||
const localContent = stringify({
|
||||
'a': 1
|
||||
});
|
||||
const remoteContent = stringify({
|
||||
'a': 2
|
||||
});
|
||||
const actual = merge(localContent, remoteContent, null, [], formattingOptions);
|
||||
assert.ok(actual.hasChanges);
|
||||
assert.ok(actual.hasConflicts);
|
||||
assert.equal(actual.mergeContent,
|
||||
`{
|
||||
<<<<<<< local
|
||||
"a": 1
|
||||
=======
|
||||
"a": 2,
|
||||
>>>>>>> remote
|
||||
}`);
|
||||
});
|
||||
|
||||
test('merge when the entry is removed in remote but updated in local and a new entry is added in remote', async () => {
|
||||
const baseContent = stringify({
|
||||
'a': 1
|
||||
});
|
||||
const localContent = stringify({
|
||||
'a': 2
|
||||
});
|
||||
const remoteContent = stringify({
|
||||
'b': 2
|
||||
});
|
||||
const actual = merge(localContent, remoteContent, baseContent, [], formattingOptions);
|
||||
assert.ok(actual.hasChanges);
|
||||
assert.ok(actual.hasConflicts);
|
||||
assert.equal(actual.mergeContent,
|
||||
`{
|
||||
<<<<<<< local
|
||||
"a": 2,
|
||||
=======
|
||||
>>>>>>> remote
|
||||
"b": 2
|
||||
}`);
|
||||
});
|
||||
|
||||
test('merge with single entry and local is empty', async () => {
|
||||
const baseContent = stringify({
|
||||
'a': 1
|
||||
});
|
||||
const localContent = stringify({});
|
||||
const remoteContent = stringify({
|
||||
'a': 2
|
||||
});
|
||||
const actual = merge(localContent, remoteContent, baseContent, [], formattingOptions);
|
||||
assert.ok(actual.hasChanges);
|
||||
assert.ok(actual.hasConflicts);
|
||||
assert.equal(actual.mergeContent,
|
||||
`{
|
||||
<<<<<<< local
|
||||
=======
|
||||
"a": 2,
|
||||
>>>>>>> remote
|
||||
}`);
|
||||
});
|
||||
|
||||
test('merge 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 actual = merge(localContent, remoteContent, baseContent, [], formattingOptions);
|
||||
assert.ok(actual.hasChanges);
|
||||
assert.ok(actual.hasConflicts);
|
||||
assert.equal(actual.mergeContent,
|
||||
`{
|
||||
<<<<<<< local
|
||||
"a": 2,
|
||||
=======
|
||||
>>>>>>> remote
|
||||
"c": 3,
|
||||
<<<<<<< local
|
||||
"d": 5,
|
||||
=======
|
||||
"d": 6,
|
||||
>>>>>>> remote
|
||||
<<<<<<< local
|
||||
"e": 4,
|
||||
=======
|
||||
"e": 5,
|
||||
>>>>>>> remote
|
||||
"f": 1
|
||||
<<<<<<< local
|
||||
=======
|
||||
"b": 3,
|
||||
>>>>>>> remote
|
||||
}`);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
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);
|
||||
assert.ok(!actual.hasChanges);
|
||||
assert.ok(!actual.hasConflicts);
|
||||
assert.equal(actual.mergeContent, localContent);
|
||||
});
|
||||
|
||||
test('ignored setting is not merged when changed in local and remote from base', async () => {
|
||||
const baseContent = stringify({ 'a': 0 });
|
||||
const localContent = stringify({ 'a': 1 });
|
||||
const remoteContent = stringify({ 'a': 2 });
|
||||
const actual = merge(localContent, remoteContent, baseContent, ['a'], formattingOptions);
|
||||
assert.ok(!actual.hasChanges);
|
||||
assert.ok(!actual.hasConflicts);
|
||||
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);
|
||||
assert.ok(!actual.hasChanges);
|
||||
assert.ok(!actual.hasConflicts);
|
||||
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);
|
||||
assert.ok(!actual.hasChanges);
|
||||
assert.ok(!actual.hasConflicts);
|
||||
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);
|
||||
assert.ok(!actual.hasChanges);
|
||||
assert.ok(!actual.hasConflicts);
|
||||
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);
|
||||
assert.ok(!actual.hasChanges);
|
||||
assert.ok(!actual.hasConflicts);
|
||||
assert.equal(actual.mergeContent, localContent);
|
||||
});
|
||||
|
||||
test('ignored setting is not merged with other changes without conflicts', async () => {
|
||||
const baseContent = stringify({
|
||||
'a': 2,
|
||||
'b': 2,
|
||||
'c': 3,
|
||||
'd': 4,
|
||||
'e': 5,
|
||||
});
|
||||
const localContent = stringify({
|
||||
'a': 1,
|
||||
'b': 2,
|
||||
'c': 3,
|
||||
});
|
||||
const remoteContent = stringify({
|
||||
'a': 3,
|
||||
'b': 3,
|
||||
'd': 4,
|
||||
'e': 6,
|
||||
});
|
||||
const expectedContent = stringify({
|
||||
'a': 1,
|
||||
'b': 3,
|
||||
});
|
||||
const actual = merge(localContent, remoteContent, baseContent, ['a', 'e'], formattingOptions);
|
||||
assert.ok(actual.hasChanges);
|
||||
assert.ok(!actual.hasConflicts);
|
||||
assert.equal(actual.mergeContent, expectedContent);
|
||||
});
|
||||
|
||||
test('ignored setting is not merged with other changes conflicts', async () => {
|
||||
const baseContent = stringify({
|
||||
'a': 2,
|
||||
'b': 2,
|
||||
'c': 3,
|
||||
'd': 4,
|
||||
'e': 5,
|
||||
});
|
||||
const localContent = stringify({
|
||||
'a': 1,
|
||||
'b': 4,
|
||||
'c': 3,
|
||||
'd': 5,
|
||||
});
|
||||
const remoteContent = stringify({
|
||||
'a': 3,
|
||||
'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}'
|
||||
assert.ok(actual.hasChanges);
|
||||
assert.ok(actual.hasChanges);
|
||||
assert.ok(actual.hasConflicts);
|
||||
assert.equal(actual.mergeContent,
|
||||
`{
|
||||
"a": 1,
|
||||
<<<<<<< local
|
||||
"b": 4,
|
||||
=======
|
||||
"b": 3,
|
||||
>>>>>>> remote
|
||||
<<<<<<< local
|
||||
"d": 5
|
||||
=======
|
||||
>>>>>>> remote
|
||||
}`);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
suite('SettingsMerge - Compute Remote Content', () => {
|
||||
|
||||
test('local content is returned when there are no ignored settings', async () => {
|
||||
const localContent = stringify({
|
||||
'a': 1,
|
||||
'b': 2,
|
||||
'c': 3,
|
||||
});
|
||||
const remoteContent = stringify({
|
||||
'a': 3,
|
||||
'b': 3,
|
||||
'd': 4,
|
||||
'e': 6,
|
||||
});
|
||||
const actual = computeRemoteContent(localContent, remoteContent, [], formattingOptions);
|
||||
assert.equal(actual, localContent);
|
||||
});
|
||||
|
||||
test('ignored settings are not updated from remote content', async () => {
|
||||
const localContent = stringify({
|
||||
'a': 1,
|
||||
'b': 2,
|
||||
'c': 3,
|
||||
});
|
||||
const remoteContent = stringify({
|
||||
'a': 3,
|
||||
'b': 3,
|
||||
'd': 4,
|
||||
'e': 6,
|
||||
});
|
||||
const expected = stringify({
|
||||
'a': 3,
|
||||
'b': 2,
|
||||
'c': 3,
|
||||
});
|
||||
const actual = computeRemoteContent(localContent, remoteContent, ['a'], formattingOptions);
|
||||
assert.equal(actual, expected);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
function stringify(value: any): string {
|
||||
return JSON.stringify(value, null, '\t');
|
||||
}
|
||||
@@ -91,7 +91,7 @@ export interface IWindowSettings {
|
||||
titleBarStyle: 'native' | 'custom';
|
||||
autoDetectHighContrast: boolean;
|
||||
menuBarVisibility: MenuBarVisibility;
|
||||
newWindowDimensions: 'default' | 'inherit' | 'maximized' | 'fullscreen';
|
||||
newWindowDimensions: 'default' | 'inherit' | 'offset' | 'maximized' | 'fullscreen';
|
||||
nativeTabs: boolean;
|
||||
nativeFullScreen: boolean;
|
||||
enableMenuBarMnemonics: boolean;
|
||||
|
||||
@@ -1381,7 +1381,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
|
||||
// Window state is not from a previous session: only allow fullscreen if we inherit it or user wants fullscreen
|
||||
let allowFullscreen: boolean;
|
||||
if (state.hasDefaultState) {
|
||||
allowFullscreen = (windowConfig?.newWindowDimensions && ['fullscreen', 'inherit'].indexOf(windowConfig.newWindowDimensions) >= 0);
|
||||
allowFullscreen = (windowConfig?.newWindowDimensions && ['fullscreen', 'inherit', 'offset'].indexOf(windowConfig.newWindowDimensions) >= 0);
|
||||
}
|
||||
|
||||
// Window state is from a previous session: only allow fullscreen when we got updated or user wants to restore
|
||||
@@ -1576,7 +1576,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
|
||||
} else if (windowConfig.newWindowDimensions === 'fullscreen') {
|
||||
state.mode = WindowMode.Fullscreen;
|
||||
ensureNoOverlap = false;
|
||||
} else if (windowConfig.newWindowDimensions === 'inherit' && lastActive) {
|
||||
} else if ((windowConfig.newWindowDimensions === 'inherit' || windowConfig.newWindowDimensions === 'offset') && lastActive) {
|
||||
const lastActiveState = lastActive.serializeWindowState();
|
||||
if (lastActiveState.mode === WindowMode.Fullscreen) {
|
||||
state.mode = WindowMode.Fullscreen; // only take mode (fixes https://github.com/Microsoft/vscode/issues/19331)
|
||||
@@ -1584,7 +1584,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
|
||||
state = lastActiveState;
|
||||
}
|
||||
|
||||
ensureNoOverlap = false;
|
||||
ensureNoOverlap = state.mode !== WindowMode.Fullscreen && windowConfig.newWindowDimensions === 'offset';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user