mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-07 09:35:41 -05:00
Merge from vscode 0a7364f00514c46c9caceece15e1f82f82e3712f
This commit is contained in:
@@ -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> {
|
||||
|
||||
Reference in New Issue
Block a user