in patched-vscode/src/vs/platform/userDataSync/common/extensionsMerge.ts [18:254]
export function merge(localExtensions: ILocalSyncExtension[], remoteExtensions: IRemoteSyncExtension[] | null, lastSyncExtensions: IRemoteSyncExtension[] | null, skippedExtensions: ISyncExtension[], ignoredExtensions: string[], lastSyncBuiltinExtensions: IExtensionIdentifier[] | null): IMergeResult {
const added: ISyncExtension[] = [];
const removed: IExtensionIdentifier[] = [];
const updated: ISyncExtension[] = [];
if (!remoteExtensions) {
const remote = localExtensions.filter(({ identifier }) => ignoredExtensions.every(id => id.toLowerCase() !== identifier.id.toLowerCase()));
return {
local: {
added,
removed,
updated,
},
remote: remote.length > 0 ? {
added: remote,
updated: [],
removed: [],
all: remote
} : null
};
}
localExtensions = localExtensions.map(massageIncomingExtension) as ILocalSyncExtension[];
remoteExtensions = remoteExtensions.map(massageIncomingExtension);
lastSyncExtensions = lastSyncExtensions ? lastSyncExtensions.map(massageIncomingExtension) : null;
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));
lastSyncExtensions?.forEach(({ identifier }) => addUUID(identifier));
skippedExtensions?.forEach(({ identifier }) => addUUID(identifier));
lastSyncBuiltinExtensions?.forEach(identifier => addUUID(identifier));
const getKey = (extension: ISyncExtension): string => {
const uuid = extension.identifier.uuid || uuids.get(extension.identifier.id.toLowerCase());
return uuid ? `uuid:${uuid}` : `id:${extension.identifier.id.toLowerCase()}`;
};
const addExtensionToMap = (map: Map<string, ISyncExtension>, extension: ISyncExtension) => {
map.set(getKey(extension), extension);
return map;
};
const localExtensionsMap: Map<string, ISyncExtension> = localExtensions.reduce(addExtensionToMap, new Map<string, ISyncExtension>());
const remoteExtensionsMap = remoteExtensions.reduce(addExtensionToMap, new Map<string, ISyncExtension>());
const newRemoteExtensionsMap = remoteExtensions.reduce((map: Map<string, ISyncExtension>, extension: ISyncExtension) => addExtensionToMap(map, deepClone(extension)), 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 lastSyncBuiltinExtensionsSet = lastSyncBuiltinExtensions ? lastSyncBuiltinExtensions.reduce((set, { id, uuid }) => {
uuid = uuid ?? uuids.get(id.toLowerCase());
return set.add(uuid ? `uuid:${uuid}` : `id:${id.toLowerCase()}`);
}, new Set<string>()) : null;
const localToRemote = compare(localExtensionsMap, remoteExtensionsMap, ignoredExtensionsSet, false);
if (localToRemote.added.size > 0 || localToRemote.removed.size > 0 || localToRemote.updated.size > 0) {
const baseToLocal = compare(lastSyncExtensionsMap, localExtensionsMap, ignoredExtensionsSet, false);
const baseToRemote = compare(lastSyncExtensionsMap, remoteExtensionsMap, ignoredExtensionsSet, true);
const merge = (key: string, localExtension: ISyncExtension, remoteExtension: ISyncExtension, preferred: ISyncExtension): ISyncExtension => {
let pinned: boolean | undefined, version: string | undefined, preRelease: boolean | undefined;
if (localExtension.installed) {
pinned = preferred.pinned;
preRelease = preferred.preRelease;
if (pinned) {
version = preferred.version;
}
} else {
pinned = remoteExtension.pinned;
preRelease = remoteExtension.preRelease;
if (pinned) {
version = remoteExtension.version;
}
}
if (pinned === undefined /* from older client*/) {
pinned = localExtension.pinned;
if (pinned) {
version = localExtension.version;
}
}
if (preRelease === undefined /* from older client*/) {
preRelease = localExtension.preRelease;
}
return {
...preferred,
installed: localExtension.installed || remoteExtension.installed,
pinned,
preRelease,
version: version ?? (remoteExtension.version && (!localExtension.installed || semver.gt(remoteExtension.version, localExtension.version)) ? remoteExtension.version : localExtension.version),
state: mergeExtensionState(localExtension, remoteExtension, lastSyncExtensionsMap?.get(key)),
};
};
// Remotely removed extension => exist in base and does not in remote
for (const key of baseToRemote.removed.values()) {
const localExtension = localExtensionsMap.get(key);
if (!localExtension) {
continue;
}
const baseExtension = assertIsDefined(lastSyncExtensionsMap?.get(key));
const wasAnInstalledExtensionDuringLastSync = lastSyncBuiltinExtensionsSet && !lastSyncBuiltinExtensionsSet.has(key) && baseExtension.installed;
if (localExtension.installed && wasAnInstalledExtensionDuringLastSync /* It is an installed extension now and during last sync */) {
// Installed extension is removed from remote. Remove it from local.
removed.push(localExtension.identifier);
} else {
// Add to remote: It is a builtin extenision or got installed after last sync
newRemoteExtensionsMap.set(key, localExtension);
}
}
// Remotely added extension => does not exist in base and exist in remote
for (const key of baseToRemote.added.values()) {
const remoteExtension = assertIsDefined(remoteExtensionsMap.get(key));
const localExtension = localExtensionsMap.get(key);
// Also exist in local
if (localExtension) {
// Is different from local to remote
if (localToRemote.updated.has(key)) {
const mergedExtension = merge(key, localExtension, remoteExtension, remoteExtension);
// Update locally only when the extension has changes in properties other than installed poperty
if (!areSame(localExtension, remoteExtension, false, false)) {
updated.push(massageOutgoingExtension(mergedExtension, key));
}
newRemoteExtensionsMap.set(key, mergedExtension);
}
} else {
// Add only if the extension is an installed extension
if (remoteExtension.installed) {
added.push(massageOutgoingExtension(remoteExtension, key));
}
}
}
// Remotely updated extension => exist in base and remote
for (const key of baseToRemote.updated.values()) {
const remoteExtension = assertIsDefined(remoteExtensionsMap.get(key));
const baseExtension = assertIsDefined(lastSyncExtensionsMap?.get(key));
const localExtension = localExtensionsMap.get(key);
// Also exist in local
if (localExtension) {
const wasAnInstalledExtensionDuringLastSync = lastSyncBuiltinExtensionsSet && !lastSyncBuiltinExtensionsSet.has(key) && baseExtension.installed;
if (wasAnInstalledExtensionDuringLastSync && localExtension.installed && !remoteExtension.installed) {
// Remove it locally if it is installed locally and not remotely
removed.push(localExtension.identifier);
} else {
// Update in local always
const mergedExtension = merge(key, localExtension, remoteExtension, remoteExtension);
updated.push(massageOutgoingExtension(mergedExtension, key));
newRemoteExtensionsMap.set(key, mergedExtension);
}
}
// Add it locally if does not exist locally and installed remotely
else if (remoteExtension.installed) {
added.push(massageOutgoingExtension(remoteExtension, key));
}
}
// Locally added extension => does not exist in base and exist in local
for (const key of baseToLocal.added.values()) {
// If added in remote (already handled)
if (baseToRemote.added.has(key)) {
continue;
}
newRemoteExtensionsMap.set(key, assertIsDefined(localExtensionsMap.get(key)));
}
// Locally updated extension => exist in base and local
for (const key of baseToLocal.updated.values()) {
// If removed in remote (already handled)
if (baseToRemote.removed.has(key)) {
continue;
}
// If updated in remote (already handled)
if (baseToRemote.updated.has(key)) {
continue;
}
const localExtension = assertIsDefined(localExtensionsMap.get(key));
const remoteExtension = assertIsDefined(remoteExtensionsMap.get(key));
// Update remotely
newRemoteExtensionsMap.set(key, merge(key, localExtension, remoteExtension, localExtension));
}
// Locally removed extensions => exist in base and does not exist in local
for (const key of baseToLocal.removed.values()) {
// If updated in remote (already handled)
if (baseToRemote.updated.has(key)) {
continue;
}
// If removed in remote (already handled)
if (baseToRemote.removed.has(key)) {
continue;
}
// Skipped
if (skippedExtensionsMap.has(key)) {
continue;
}
// Skip if it is a builtin extension
if (!assertIsDefined(remoteExtensionsMap.get(key)).installed) {
continue;
}
// Skip if last sync builtin extensions set is not available
if (!lastSyncBuiltinExtensionsSet) {
continue;
}
// Skip if it was a builtin extension during last sync
if (lastSyncBuiltinExtensionsSet.has(key) || !assertIsDefined(lastSyncExtensionsMap?.get(key)).installed) {
continue;
}
newRemoteExtensionsMap.delete(key);
}
}
const remote: ISyncExtension[] = [];
const remoteChanges = compare(remoteExtensionsMap, newRemoteExtensionsMap, new Set<string>(), true);
const hasRemoteChanges = remoteChanges.added.size > 0 || remoteChanges.updated.size > 0 || remoteChanges.removed.size > 0;
if (hasRemoteChanges) {
newRemoteExtensionsMap.forEach((value, key) => remote.push(massageOutgoingExtension(value, key)));
}
return {
local: { added, removed, updated },
remote: hasRemoteChanges ? {
added: [...remoteChanges.added].map(id => newRemoteExtensionsMap.get(id)!),
updated: [...remoteChanges.updated].map(id => newRemoteExtensionsMap.get(id)!),
removed: [...remoteChanges.removed].map(id => remoteExtensionsMap.get(id)!),
all: remote
} : null
};
}