Merge from vscode 0a7364f00514c46c9caceece15e1f82f82e3712f

This commit is contained in:
ADS Merger
2020-07-22 03:06:57 +00:00
parent 53ec7585a9
commit 1b7b54ce14
229 changed files with 5099 additions and 3188 deletions

View File

@@ -10,7 +10,7 @@ import { URI } from 'vs/base/common/uri';
import {
SyncResource, SyncStatus, IUserData, IUserDataSyncStoreService, UserDataSyncErrorCode, UserDataSyncError, IUserDataSyncLogService, IUserDataSyncUtilService,
IUserDataSyncResourceEnablementService, IUserDataSyncBackupStoreService, ISyncResourceHandle, USER_DATA_SYNC_SCHEME, ISyncResourcePreview as IBaseSyncResourcePreview,
IUserDataManifest, ISyncData, IRemoteUserData, PREVIEW_DIR_NAME, IResourcePreview as IBaseResourcePreview, Change
IUserDataManifest, ISyncData, IRemoteUserData, PREVIEW_DIR_NAME, IResourcePreview as IBaseResourcePreview, Change, MergeState
} from 'vs/platform/userDataSync/common/userDataSync';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { joinPath, dirname, isEqual, basename } from 'vs/base/common/resources';
@@ -57,11 +57,11 @@ export interface IMergableResourcePreview extends IBaseResourcePreview {
readonly remoteContent: string | null;
readonly localContent: string | null;
readonly previewContent: string | null;
readonly acceptedContent: string | null;
readonly hasConflicts: boolean;
merged: boolean;
}
export type IResourcePreview = Omit<IMergableResourcePreview, 'merged'>;
export type IResourcePreview = Omit<IMergableResourcePreview, 'mergeState'>;
export interface ISyncResourcePreview extends IBaseSyncResourcePreview {
readonly remoteUserData: IRemoteUserData;
@@ -217,6 +217,19 @@ export abstract class AbstractSynchroniser extends Disposable {
return this._sync(manifest, false, headers);
}
async apply(force: boolean, headers: IHeaders = {}): Promise<ISyncResourcePreview | null> {
try {
this.syncHeaders = { ...headers };
const status = await this.doApply(force);
this.setStatus(status);
return this.syncPreviewPromise;
} finally {
this.syncHeaders = {};
}
}
private async _sync(manifest: IUserDataManifest | null, apply: boolean, headers: IHeaders): Promise<ISyncResourcePreview | null> {
try {
this.syncHeaders = { ...headers };
@@ -350,14 +363,18 @@ export abstract class AbstractSynchroniser extends Disposable {
this.syncPreviewPromise = createCancelablePromise(token => this.doGenerateSyncResourcePreview(remoteUserData, lastSyncUserData, apply, token));
}
if (apply) {
const preview = await this.syncPreviewPromise;
const newConflicts = preview.resourcePreviews.filter(({ hasConflicts }) => hasConflicts);
return await this.updateConflictsAndApply(newConflicts, false);
} else {
return SyncStatus.Syncing;
const preview = await this.syncPreviewPromise;
this.updateConflicts(preview.resourcePreviews);
if (preview.resourcePreviews.some(({ mergeState }) => mergeState === MergeState.Conflict)) {
return SyncStatus.HasConflicts;
}
if (apply) {
return await this.doApply(false);
}
return SyncStatus.Syncing;
} catch (error) {
// reset preview on error
@@ -367,76 +384,91 @@ export abstract class AbstractSynchroniser extends Disposable {
}
}
async acceptPreviewContent(resource: URI, content: string, force: boolean, headers: IHeaders = {}): Promise<ISyncResourcePreview | null> {
async accept(resource: URI, content: string | null): Promise<ISyncResourcePreview | null> {
await this.updateSyncResourcePreview(resource, async (resourcePreview) => {
const updatedResourcePreview = await this.updateResourcePreview(resourcePreview, resource, content);
return {
...updatedResourcePreview,
mergeState: MergeState.Accepted
};
});
return this.syncPreviewPromise;
}
async merge(resource: URI): Promise<ISyncResourcePreview | null> {
await this.updateSyncResourcePreview(resource, async (resourcePreview) => {
const updatedResourcePreview = await this.updateResourcePreview(resourcePreview, resourcePreview.previewResource, resourcePreview.previewContent);
return {
...updatedResourcePreview,
mergeState: resourcePreview.hasConflicts ? MergeState.Conflict : MergeState.Accepted
};
});
return this.syncPreviewPromise;
}
async discard(resource: URI): Promise<ISyncResourcePreview | null> {
await this.updateSyncResourcePreview(resource, async (resourcePreview) => {
await this.fileService.writeFile(resourcePreview.previewResource, VSBuffer.fromString(resourcePreview.previewContent || ''));
const updatedResourcePreview = await this.updateResourcePreview(resourcePreview, resourcePreview.previewResource, resourcePreview.previewContent);
return {
...updatedResourcePreview,
mergeState: MergeState.Preview
};
});
return this.syncPreviewPromise;
}
private async updateSyncResourcePreview(resource: URI, updateResourcePreview: (resourcePreview: IMergableResourcePreview) => Promise<IMergableResourcePreview>): Promise<void> {
if (!this.syncPreviewPromise) {
return null;
return;
}
try {
this.syncHeaders = { ...headers };
const preview = await this.syncPreviewPromise;
this.syncPreviewPromise = createCancelablePromise(token => this.updateSyncResourcePreviewContent(preview, resource, content, token));
return this.merge(resource, force, headers);
} finally {
this.syncHeaders = {};
let preview = await this.syncPreviewPromise;
const index = preview.resourcePreviews.findIndex(({ localResource, remoteResource, previewResource }) =>
isEqual(localResource, resource) || isEqual(remoteResource, resource) || isEqual(previewResource, resource));
if (index === -1) {
return;
}
this.syncPreviewPromise = createCancelablePromise(async token => {
const resourcePreviews = [...preview.resourcePreviews];
resourcePreviews[index] = await updateResourcePreview(resourcePreviews[index]);
return {
...preview,
resourcePreviews
};
});
preview = await this.syncPreviewPromise;
this.updateConflicts(preview.resourcePreviews);
if (preview.resourcePreviews.some(({ mergeState }) => mergeState === MergeState.Conflict)) {
this.setStatus(SyncStatus.HasConflicts);
} else {
this.setStatus(SyncStatus.Syncing);
}
}
async merge(resource: URI, force: boolean, headers: IHeaders = {}): Promise<ISyncResourcePreview | null> {
if (!this.syncPreviewPromise) {
return null;
}
try {
this.syncHeaders = { ...headers };
const preview = await this.syncPreviewPromise;
const resourcePreview = preview.resourcePreviews.find(({ localResource, remoteResource, previewResource }) =>
isEqual(localResource, resource) || isEqual(remoteResource, resource) || isEqual(previewResource, resource));
if (!resourcePreview) {
return preview;
}
/* mark merged */
resourcePreview.merged = true;
/* Add or remove the preview from conflicts */
const newConflicts = [...this._conflicts];
const index = newConflicts.findIndex(({ localResource, remoteResource, previewResource }) =>
isEqual(localResource, resource) || isEqual(remoteResource, resource) || isEqual(previewResource, resource));
if (resourcePreview.hasConflicts) {
if (newConflicts.indexOf(resourcePreview) === -1) {
newConflicts.push(resourcePreview);
}
} else {
if (index !== -1) {
newConflicts.splice(index, 1);
}
}
const status = await this.updateConflictsAndApply(newConflicts, force);
this.setStatus(status);
return this.syncPreviewPromise;
} finally {
this.syncHeaders = {};
}
protected async updateResourcePreview(resourcePreview: IResourcePreview, resource: URI, acceptedContent: string | null): Promise<IResourcePreview> {
return {
...resourcePreview,
acceptedContent
};
}
private async updateConflictsAndApply(conflicts: IMergableResourcePreview[], force: boolean): Promise<SyncStatus> {
private async doApply(force: boolean): Promise<SyncStatus> {
if (!this.syncPreviewPromise) {
return SyncStatus.Idle;
}
const preview = await this.syncPreviewPromise;
// update conflicts
this.updateConflicts(conflicts);
if (this._conflicts.length) {
// check for conflicts
if (preview.resourcePreviews.some(({ mergeState }) => mergeState === MergeState.Conflict)) {
return SyncStatus.HasConflicts;
}
// check if all are merged
if (preview.resourcePreviews.some(r => !r.merged)) {
// check if all are accepted
if (preview.resourcePreviews.some(({ mergeState }) => mergeState !== MergeState.Accepted)) {
return SyncStatus.Syncing;
}
@@ -452,39 +484,14 @@ export abstract class AbstractSynchroniser extends Disposable {
return SyncStatus.Idle;
}
private async updateSyncResourcePreviewContent(preview: ISyncResourcePreview, resource: URI, previewContent: string, token: CancellationToken): Promise<ISyncResourcePreview> {
const index = preview.resourcePreviews.findIndex(({ localResource, remoteResource, previewResource, localChange, remoteChange }) =>
(localChange !== Change.None || remoteChange !== Change.None)
&& (isEqual(localResource, resource) || isEqual(remoteResource, resource) || isEqual(previewResource, resource)));
if (index !== -1) {
const resourcePreviews = [...preview.resourcePreviews];
const resourcePreview = await this.updateResourcePreviewContent(resourcePreviews[index], resource, previewContent, token);
resourcePreviews[index] = { ...resourcePreview, merged: resourcePreviews[index].merged };
preview = {
...preview,
resourcePreviews
};
}
return preview;
}
protected async updateResourcePreviewContent(resourcePreview: IResourcePreview, resource: URI, previewContent: string, token: CancellationToken): Promise<IResourcePreview> {
return {
...resourcePreview,
previewContent,
hasConflicts: false,
localChange: Change.Modified,
remoteChange: Change.Modified,
};
}
private async clearPreviewFolder(): Promise<void> {
try {
await this.fileService.del(this.syncPreviewFolder, { recursive: true });
} catch (error) { /* Ignore */ }
}
private updateConflicts(conflicts: IMergableResourcePreview[]): void {
private updateConflicts(previews: IMergableResourcePreview[]): void {
const conflicts = previews.filter(p => p.mergeState === MergeState.Conflict);
if (!equals(this._conflicts, conflicts, (a, b) => isEqual(a.previewResource, b.previewResource))) {
this._conflicts = conflicts;
this._onDidChangeConflicts.fire(conflicts);
@@ -542,14 +549,14 @@ export abstract class AbstractSynchroniser extends Disposable {
const syncPreview = this.syncPreviewPromise ? await this.syncPreviewPromise : null;
if (syncPreview) {
for (const resourcePreview of syncPreview.resourcePreviews) {
if (resourcePreview.previewResource && isEqual(resourcePreview.previewResource, uri)) {
return resourcePreview.previewContent || '';
if (isEqual(resourcePreview.acceptedResource, uri)) {
return resourcePreview.acceptedContent;
}
if (resourcePreview.remoteResource && isEqual(resourcePreview.remoteResource, uri)) {
return resourcePreview.remoteContent || '';
if (isEqual(resourcePreview.remoteResource, uri)) {
return resourcePreview.remoteContent;
}
if (resourcePreview.localResource && isEqual(resourcePreview.localResource, uri)) {
return resourcePreview.localContent || '';
if (isEqual(resourcePreview.localResource, uri)) {
return resourcePreview.localContent;
}
}
}
@@ -562,21 +569,31 @@ export abstract class AbstractSynchroniser extends Disposable {
} catch (e) { /* ignore */ }
}
private async doGenerateSyncResourcePreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, merge: boolean, token: CancellationToken): Promise<ISyncResourcePreview> {
private async doGenerateSyncResourcePreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, apply: boolean, token: CancellationToken): Promise<ISyncResourcePreview> {
const machineId = await this.currentMachineIdPromise;
const isLastSyncFromCurrentMachine = !!remoteUserData.syncData?.machineId && remoteUserData.syncData.machineId === machineId;
// For preview, use remoteUserData if lastSyncUserData does not exists and last sync is from current machine
const lastSyncUserDataForPreview = lastSyncUserData === null && isLastSyncFromCurrentMachine ? remoteUserData : lastSyncUserData;
const resourcePreviews = await this.generateSyncPreview(remoteUserData, lastSyncUserDataForPreview, token);
const result = await this.generateSyncPreview(remoteUserData, lastSyncUserDataForPreview, token);
// Mark merge
const mergableResourcePreviews = resourcePreviews.map(r => ({
...r,
merged: merge || (r.localChange === Change.None && r.remoteChange === Change.None) /* Mark previews with no changes as merged */
}));
const resourcePreviews: IMergableResourcePreview[] = [];
for (const resourcePreview of result) {
if (token.isCancellationRequested) {
break;
}
if (!apply) {
await this.fileService.writeFile(resourcePreview.previewResource, VSBuffer.fromString(resourcePreview.previewContent || ''));
}
resourcePreviews.push({
...resourcePreview,
mergeState: resourcePreview.localChange === Change.None && resourcePreview.remoteChange === Change.None ? MergeState.Accepted /* Mark previews with no changes as merged */
: apply ? (resourcePreview.hasConflicts ? MergeState.Conflict : MergeState.Accepted)
: MergeState.Preview
});
}
return { remoteUserData, lastSyncUserData, resourcePreviews: mergableResourcePreviews, isLastSyncFromCurrentMachine };
return { remoteUserData, lastSyncUserData, resourcePreviews, isLastSyncFromCurrentMachine };
}
async getLastSyncUserData<T extends IRemoteUserData>(): Promise<T | null> {