Skip to content

Commit b50e599

Browse files
committed
1 parent db51424 commit b50e599

5 files changed

Lines changed: 158 additions & 79 deletions

File tree

src/vs/platform/userDataSync/common/userDataSync.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -389,12 +389,14 @@ export interface ISyncTask {
389389

390390
export interface IManualSyncTask extends IDisposable {
391391
readonly id: string;
392+
readonly status: SyncStatus;
392393
readonly manifest: IUserDataManifest | null;
393394
readonly onSynchronizeResources: Event<[SyncResource, URI[]][]>;
394395
preview(): Promise<[SyncResource, ISyncResourcePreview][]>;
395396
accept(resource: URI, content?: string | null): Promise<[SyncResource, ISyncResourcePreview][]>;
396-
merge(resource: URI): Promise<[SyncResource, ISyncResourcePreview][]>;
397+
merge(resource?: URI): Promise<[SyncResource, ISyncResourcePreview][]>;
397398
discard(resource: URI): Promise<[SyncResource, ISyncResourcePreview][]>;
399+
discardConflicts(): Promise<[SyncResource, ISyncResourcePreview][]>;
398400
apply(): Promise<[SyncResource, ISyncResourcePreview][]>;
399401
pull(): Promise<void>;
400402
push(): Promise<void>;

src/vs/platform/userDataSync/common/userDataSyncIpc.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
import { IServerChannel, IChannel, IPCServer } from 'vs/base/parts/ipc/common/ipc';
77
import { Event, Emitter } from 'vs/base/common/event';
8-
import { IUserDataSyncService, IUserDataSyncUtilService, IUserDataAutoSyncService, IManualSyncTask, IUserDataManifest, IUserDataSyncStoreManagementService } from 'vs/platform/userDataSync/common/userDataSync';
8+
import { IUserDataSyncService, IUserDataSyncUtilService, IUserDataAutoSyncService, IManualSyncTask, IUserDataManifest, IUserDataSyncStoreManagementService, SyncStatus } from 'vs/platform/userDataSync/common/userDataSync';
99
import { URI } from 'vs/base/common/uri';
1010
import { IStringDictionary } from 'vs/base/common/collections';
1111
import { FormattingOptions } from 'vs/base/common/jsonFormatter';
@@ -64,11 +64,11 @@ export class UserDataSyncChannel implements IServerChannel {
6464
throw new Error('Invalid call');
6565
}
6666

67-
private async createManualSyncTask(): Promise<{ id: string, manifest: IUserDataManifest | null }> {
67+
private async createManualSyncTask(): Promise<{ id: string, manifest: IUserDataManifest | null, status: SyncStatus }> {
6868
const manualSyncTask = await this.service.createManualSyncTask();
6969
const manualSyncTaskChannel = new ManualSyncTaskChannel(manualSyncTask, this.logService);
7070
this.server.registerChannel(`manualSyncTask-${manualSyncTask.id}`, manualSyncTaskChannel);
71-
return { id: manualSyncTask.id, manifest: manualSyncTask.manifest };
71+
return { id: manualSyncTask.id, manifest: manualSyncTask.manifest, status: manualSyncTask.status };
7272
}
7373
}
7474

@@ -102,10 +102,12 @@ class ManualSyncTaskChannel implements IServerChannel {
102102
case 'accept': return this.manualSyncTask.accept(URI.revive(args[0]), args[1]);
103103
case 'merge': return this.manualSyncTask.merge(URI.revive(args[0]));
104104
case 'discard': return this.manualSyncTask.discard(URI.revive(args[0]));
105+
case 'discardConflicts': return this.manualSyncTask.discardConflicts();
105106
case 'apply': return this.manualSyncTask.apply();
106107
case 'pull': return this.manualSyncTask.pull();
107108
case 'push': return this.manualSyncTask.push();
108109
case 'stop': return this.manualSyncTask.stop();
110+
case '_getStatus': return this.manualSyncTask.status;
109111
case 'dispose': return this.manualSyncTask.dispose();
110112
}
111113
throw new Error('Invalid call');

src/vs/platform/userDataSync/common/userDataSyncService.ts

Lines changed: 110 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -412,6 +412,16 @@ class ManualSyncTask extends Disposable implements IManualSyncTask {
412412

413413
private isDisposed: boolean = false;
414414

415+
get status(): SyncStatus {
416+
if (this.synchronisers.some(s => s.status === SyncStatus.HasConflicts)) {
417+
return SyncStatus.HasConflicts;
418+
}
419+
if (this.synchronisers.some(s => s.status === SyncStatus.Syncing)) {
420+
return SyncStatus.Syncing;
421+
}
422+
return SyncStatus.Idle;
423+
}
424+
415425
constructor(
416426
readonly id: string,
417427
readonly manifest: IUserDataManifest | null,
@@ -429,22 +439,110 @@ class ManualSyncTask extends Disposable implements IManualSyncTask {
429439
if (!this.previewsPromise) {
430440
this.previewsPromise = createCancelablePromise(token => this.getPreviews(token));
431441
}
432-
this.previews = await this.previewsPromise;
442+
if (!this.previews) {
443+
this.previews = await this.previewsPromise;
444+
}
433445
return this.previews;
434446
}
435447

436448
async accept(resource: URI, content?: string | null): Promise<[SyncResource, ISyncResourcePreview][]> {
437449
return this.performAction(resource, sychronizer => sychronizer.accept(resource, content));
438450
}
439451

440-
async merge(resource: URI): Promise<[SyncResource, ISyncResourcePreview][]> {
441-
return this.performAction(resource, sychronizer => sychronizer.merge(resource));
452+
async merge(resource?: URI): Promise<[SyncResource, ISyncResourcePreview][]> {
453+
if (resource) {
454+
return this.performAction(resource, sychronizer => sychronizer.merge(resource));
455+
} else {
456+
return this.doMerge(false);
457+
}
442458
}
443459

444460
async discard(resource: URI): Promise<[SyncResource, ISyncResourcePreview][]> {
445461
return this.performAction(resource, sychronizer => sychronizer.discard(resource));
446462
}
447463

464+
async discardConflicts(): Promise<[SyncResource, ISyncResourcePreview][]> {
465+
if (!this.previews) {
466+
throw new Error('Missing preview. Create preview and try again.');
467+
}
468+
if (this.synchronizingResources.length) {
469+
throw new Error('Cannot discard while synchronizing resources');
470+
}
471+
472+
const conflictResources: URI[] = [];
473+
for (const [, syncResourcePreview] of this.previews) {
474+
for (const resourcePreview of syncResourcePreview.resourcePreviews) {
475+
if (resourcePreview.mergeState === MergeState.Conflict) {
476+
conflictResources.push(resourcePreview.previewResource);
477+
}
478+
}
479+
}
480+
481+
for (const resource of conflictResources) {
482+
await this.discard(resource);
483+
}
484+
return this.previews;
485+
}
486+
487+
async apply(): Promise<[SyncResource, ISyncResourcePreview][]> {
488+
return this.doMerge(true);
489+
}
490+
491+
async pull(): Promise<void> {
492+
if (!this.previews) {
493+
throw new Error('You need to create preview before applying');
494+
}
495+
if (this.synchronizingResources.length) {
496+
throw new Error('Cannot pull while synchronizing resources');
497+
}
498+
for (const [syncResource, preview] of this.previews) {
499+
this.synchronizingResources.push([syncResource, preview.resourcePreviews.map(r => r.localResource)]);
500+
this._onSynchronizeResources.fire(this.synchronizingResources);
501+
const synchroniser = this.synchronisers.find(s => s.resource === syncResource)!;
502+
for (const resourcePreview of preview.resourcePreviews) {
503+
await synchroniser.accept(resourcePreview.remoteResource);
504+
}
505+
await synchroniser.apply(true, this.syncHeaders);
506+
this.synchronizingResources.splice(this.synchronizingResources.findIndex(s => s[0] === syncResource), 1);
507+
this._onSynchronizeResources.fire(this.synchronizingResources);
508+
}
509+
this.previews = [];
510+
}
511+
512+
async push(): Promise<void> {
513+
if (!this.previews) {
514+
throw new Error('You need to create preview before applying');
515+
}
516+
if (this.synchronizingResources.length) {
517+
throw new Error('Cannot pull while synchronizing resources');
518+
}
519+
for (const [syncResource, preview] of this.previews) {
520+
this.synchronizingResources.push([syncResource, preview.resourcePreviews.map(r => r.localResource)]);
521+
this._onSynchronizeResources.fire(this.synchronizingResources);
522+
const synchroniser = this.synchronisers.find(s => s.resource === syncResource)!;
523+
for (const resourcePreview of preview.resourcePreviews) {
524+
await synchroniser.accept(resourcePreview.localResource);
525+
}
526+
await synchroniser.apply(true, this.syncHeaders);
527+
this.synchronizingResources.splice(this.synchronizingResources.findIndex(s => s[0] === syncResource), 1);
528+
this._onSynchronizeResources.fire(this.synchronizingResources);
529+
}
530+
this.previews = [];
531+
}
532+
533+
async stop(): Promise<void> {
534+
for (const synchroniser of this.synchronisers) {
535+
try {
536+
await synchroniser.stop();
537+
} catch (error) {
538+
if (!isPromiseCanceledError(error)) {
539+
this.logService.error(error);
540+
}
541+
}
542+
}
543+
this.reset();
544+
}
545+
448546
private async performAction(resource: URI, action: (synchroniser: IUserDataSynchroniser) => Promise<ISyncResourcePreview | null>): Promise<[SyncResource, ISyncResourcePreview][]> {
449547
if (!this.previews) {
450548
throw new Error('Missing preview. Create preview and try again.');
@@ -486,12 +584,12 @@ class ManualSyncTask extends Disposable implements IManualSyncTask {
486584
return this.previews;
487585
}
488586

489-
async apply(): Promise<[SyncResource, ISyncResourcePreview][]> {
587+
private async doMerge(apply: boolean): Promise<[SyncResource, ISyncResourcePreview][]> {
490588
if (!this.previews) {
491-
throw new Error('You need to create preview before applying');
589+
throw new Error('You need to create preview before merging or applying');
492590
}
493591
if (this.synchronizingResources.length) {
494-
throw new Error('Cannot pull while synchronizing resources');
592+
throw new Error('Cannot merge or apply while synchronizing resources');
495593
}
496594
const previews: [SyncResource, ISyncResourcePreview][] = [];
497595
for (const [syncResource, preview] of this.previews) {
@@ -501,14 +599,18 @@ class ManualSyncTask extends Disposable implements IManualSyncTask {
501599
const synchroniser = this.synchronisers.find(s => s.resource === syncResource)!;
502600

503601
/* merge those which are not yet merged */
602+
let newPreview: ISyncResourcePreview | null = null;
504603
for (const resourcePreview of preview.resourcePreviews) {
505604
if ((resourcePreview.localChange !== Change.None || resourcePreview.remoteChange !== Change.None) && resourcePreview.mergeState === MergeState.Preview) {
506-
await synchroniser.merge(resourcePreview.previewResource);
605+
newPreview = await synchroniser.merge(resourcePreview.previewResource);
507606
}
508607
}
509608

510609
/* apply */
511-
const newPreview = await synchroniser.apply(false, this.syncHeaders);
610+
if (apply) {
611+
newPreview = await synchroniser.apply(false, this.syncHeaders);
612+
}
613+
512614
if (newPreview) {
513615
previews.push(this.toSyncResourcePreview(synchroniser.resource, newPreview));
514616
}
@@ -520,61 +622,6 @@ class ManualSyncTask extends Disposable implements IManualSyncTask {
520622
return this.previews;
521623
}
522624

523-
async pull(): Promise<void> {
524-
if (!this.previews) {
525-
throw new Error('You need to create preview before applying');
526-
}
527-
if (this.synchronizingResources.length) {
528-
throw new Error('Cannot pull while synchronizing resources');
529-
}
530-
for (const [syncResource, preview] of this.previews) {
531-
this.synchronizingResources.push([syncResource, preview.resourcePreviews.map(r => r.localResource)]);
532-
this._onSynchronizeResources.fire(this.synchronizingResources);
533-
const synchroniser = this.synchronisers.find(s => s.resource === syncResource)!;
534-
for (const resourcePreview of preview.resourcePreviews) {
535-
await synchroniser.accept(resourcePreview.remoteResource);
536-
}
537-
await synchroniser.apply(true, this.syncHeaders);
538-
this.synchronizingResources.splice(this.synchronizingResources.findIndex(s => s[0] === syncResource), 1);
539-
this._onSynchronizeResources.fire(this.synchronizingResources);
540-
}
541-
this.previews = [];
542-
}
543-
544-
async push(): Promise<void> {
545-
if (!this.previews) {
546-
throw new Error('You need to create preview before applying');
547-
}
548-
if (this.synchronizingResources.length) {
549-
throw new Error('Cannot pull while synchronizing resources');
550-
}
551-
for (const [syncResource, preview] of this.previews) {
552-
this.synchronizingResources.push([syncResource, preview.resourcePreviews.map(r => r.localResource)]);
553-
this._onSynchronizeResources.fire(this.synchronizingResources);
554-
const synchroniser = this.synchronisers.find(s => s.resource === syncResource)!;
555-
for (const resourcePreview of preview.resourcePreviews) {
556-
await synchroniser.accept(resourcePreview.localResource);
557-
}
558-
await synchroniser.apply(true, this.syncHeaders);
559-
this.synchronizingResources.splice(this.synchronizingResources.findIndex(s => s[0] === syncResource), 1);
560-
this._onSynchronizeResources.fire(this.synchronizingResources);
561-
}
562-
this.previews = [];
563-
}
564-
565-
async stop(): Promise<void> {
566-
for (const synchroniser of this.synchronisers) {
567-
try {
568-
await synchroniser.stop();
569-
} catch (error) {
570-
if (!isPromiseCanceledError(error)) {
571-
this.logService.error(error);
572-
}
573-
}
574-
}
575-
this.reset();
576-
}
577-
578625
private async getPreviews(token: CancellationToken): Promise<[SyncResource, ISyncResourcePreview][]> {
579626
const result: [SyncResource, ISyncResourcePreview][] = [];
580627
for (const synchroniser of this.synchronisers) {

src/vs/workbench/services/userDataSync/browser/userDataSyncWorkbenchService.ts

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
* Licensed under the MIT License. See License.txt in the project root for license information.
44
*--------------------------------------------------------------------------------------------*/
55

6-
import { IUserDataSyncService, IAuthenticationProvider, isAuthenticationProvider, IUserDataAutoSyncService, SyncResource, IResourcePreview, ISyncResourcePreview, Change, IManualSyncTask, IUserDataSyncStoreManagementService, UserDataSyncStoreType } from 'vs/platform/userDataSync/common/userDataSync';
6+
import { IUserDataSyncService, IAuthenticationProvider, isAuthenticationProvider, IUserDataAutoSyncService, SyncResource, IResourcePreview, ISyncResourcePreview, Change, IManualSyncTask, IUserDataSyncStoreManagementService, UserDataSyncStoreType, SyncStatus } from 'vs/platform/userDataSync/common/userDataSync';
77
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
88
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
99
import { IUserDataSyncWorkbenchService, IUserDataSyncAccount, AccountStatus, CONTEXT_SYNC_ENABLEMENT, CONTEXT_SYNC_STATE, CONTEXT_ACCOUNT_STATE, SHOW_SYNC_LOG_COMMAND_ID, getSyncAreaLabel, IUserDataSyncPreview, IUserDataSyncResource, CONTEXT_ENABLE_SYNC_MERGES_VIEW, SYNC_MERGES_VIEW_ID, CONTEXT_ENABLE_ACTIVITY_VIEWS, SYNC_VIEW_CONTAINER_ID, SYNC_TITLE } from 'vs/workbench/services/userDataSync/common/userDataSync';
@@ -268,7 +268,6 @@ export class UserDataSyncWorkbenchService extends Disposable implements IUserDat
268268
const manualSyncTask = await this.userDataSyncService.createManualSyncTask();
269269
try {
270270
let action: FirstTimeSyncAction = 'manual';
271-
let preview: [SyncResource, ISyncResourcePreview][] = [];
272271

273272
await this.progressService.withProgress({
274273
location: ProgressLocation.Notification,
@@ -277,7 +276,7 @@ export class UserDataSyncWorkbenchService extends Disposable implements IUserDat
277276
}, async progress => {
278277
progress.report({ message: localize('turning on', "Turning on...") });
279278

280-
preview = await manualSyncTask.preview();
279+
const preview = await manualSyncTask.preview();
281280
const hasRemoteData = manualSyncTask.manifest !== null;
282281
const hasLocalData = await this.userDataSyncService.hasLocalData();
283282
const hasMergesFromAnotherMachine = preview.some(([syncResource, { isLastSyncFromCurrentMachine, resourcePreviews }]) =>
@@ -289,7 +288,12 @@ export class UserDataSyncWorkbenchService extends Disposable implements IUserDat
289288
synchronizingResources.length ? progress.report({ message: localize('syncing resource', "Syncing {0}...", getSyncAreaLabel(synchronizingResources[0][0])) }) : undefined);
290289
try {
291290
switch (action) {
292-
case 'merge': return await manualSyncTask.apply();
291+
case 'merge':
292+
await manualSyncTask.merge();
293+
if (manualSyncTask.status !== SyncStatus.HasConflicts) {
294+
await manualSyncTask.apply();
295+
}
296+
return;
293297
case 'pull': return await manualSyncTask.pull();
294298
case 'push': return await manualSyncTask.push();
295299
case 'manual': return;
@@ -298,8 +302,15 @@ export class UserDataSyncWorkbenchService extends Disposable implements IUserDat
298302
progressDisposable.dispose();
299303
}
300304
});
305+
if (manualSyncTask.status === SyncStatus.HasConflicts) {
306+
await this.dialogService.show(Severity.Warning, localize('conflicts detected', "Conflicts Detected"), [], {
307+
detail: localize('resolve', "Unable to merge due to conflicts. Please resolve them to continue.")
308+
});
309+
await manualSyncTask.discardConflicts();
310+
action = 'manual';
311+
}
301312
if (action === 'manual') {
302-
await this.syncManually(manualSyncTask, preview);
313+
await this.syncManually(manualSyncTask);
303314
}
304315
} catch (error) {
305316
await manualSyncTask.stop();
@@ -347,8 +358,9 @@ export class UserDataSyncWorkbenchService extends Disposable implements IUserDat
347358
throw canceled();
348359
}
349360

350-
private async syncManually(task: IManualSyncTask, preview: [SyncResource, ISyncResourcePreview][]): Promise<void> {
361+
private async syncManually(task: IManualSyncTask): Promise<void> {
351362
const visibleViewContainer = this.viewsService.getVisibleViewContainer(ViewContainerLocation.Sidebar);
363+
const preview = await task.preview();
352364
this.userDataSyncPreview.setManualSyncPreview(task, preview);
353365

354366
this.mergesViewEnablementContext.set(true);

0 commit comments

Comments
 (0)