Explorer: copy() of dirty file reverts the source if dirty (fix #89217)

This commit is contained in:
Benjamin Pasero 2020-01-24 11:37:10 +01:00
parent 4ba2070818
commit 10cbb8533f
3 changed files with 45 additions and 15 deletions

View file

@ -192,12 +192,12 @@ export abstract class AbstractTextFileService extends Disposable implements ITex
// find all models that related to either source or target (can be many if resource is a folder)
const sourceModels: ITextFileEditorModel[] = [];
const conflictingModels: ITextFileEditorModel[] = [];
const targetModels: ITextFileEditorModel[] = [];
for (const model of this.getFileModels()) {
const resource = model.resource;
if (isEqualOrParent(resource, target, false /* do not ignorecase, see https://github.com/Microsoft/vscode/issues/56384 */)) {
conflictingModels.push(model);
targetModels.push(model);
}
if (isEqualOrParent(resource, source)) {
@ -232,10 +232,11 @@ export abstract class AbstractTextFileService extends Disposable implements ITex
modelsToRestore.push(modelToRestore);
}
// in order to move and copy, we need to soft revert all dirty models,
// both from the source as well as the target if any
const dirtyModels = [...sourceModels, ...conflictingModels].filter(model => model.isDirty());
await this.doRevertFiles(dirtyModels.map(dirtyModel => dirtyModel.resource), { soft: true });
// handle dirty models depending on the operation:
// - move: revert both source and target (if any)
// - copy: revert target (if any)
const dirtyModelsToRevert = (move ? [...sourceModels, ...targetModels] : [...targetModels]).filter(model => model.isDirty());
await this.doRevertFiles(dirtyModelsToRevert.map(dirtyModel => dirtyModel.resource), { soft: true });
// now we can rename the source to target via file operation
let stat: IFileStatWithMetadata;
@ -248,7 +249,7 @@ export abstract class AbstractTextFileService extends Disposable implements ITex
} catch (error) {
// in case of any error, ensure to set dirty flag back
dirtyModels.forEach(dirtyModel => dirtyModel.makeDirty());
dirtyModelsToRevert.forEach(dirtyModel => dirtyModel.makeDirty());
throw error;
}

View file

@ -5,7 +5,7 @@
import * as assert from 'assert';
import { URI } from 'vs/base/common/uri';
import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle';
import { workbenchInstantiationService, TestLifecycleService, TestTextFileService, TestContextService, TestFileService, TestElectronService, TestFilesConfigurationService, TestFileDialogService } from 'vs/workbench/test/workbenchTestServices';
import { workbenchInstantiationService, TestLifecycleService, TestContextService, TestFileService, TestElectronService, TestFilesConfigurationService, TestFileDialogService, TestTextFileService } from 'vs/workbench/test/workbenchTestServices';
import { toResource } from 'vs/base/test/common/utils';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textFileEditorModel';
@ -126,6 +126,19 @@ suite('Files - TextFileService', () => {
assert.ok(!accessor.textFileService.isDirty(model.resource));
});
test('create', async function () {
model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8', undefined);
(<TextFileEditorModelManager>accessor.textFileService.files).add(model.resource, model);
await model.load();
model!.textEditorModel!.setValue('foo');
assert.ok(accessor.textFileService.isDirty(model.resource));
await accessor.textFileService.create(model.resource, 'Foo');
assert.ok(!accessor.textFileService.isDirty(model.resource));
});
test('delete - dirty file', async function () {
model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8', undefined);
(<TextFileEditorModelManager>accessor.textFileService.files).add(model.resource, model);
@ -139,14 +152,22 @@ suite('Files - TextFileService', () => {
});
test('move - dirty file', async function () {
await testMove(toResource.call(this, '/path/file.txt'), toResource.call(this, '/path/file_target.txt'));
await testMoveOrCopy(toResource.call(this, '/path/file.txt'), toResource.call(this, '/path/file_target.txt'), true);
});
test('move - dirty file (target exists and is dirty)', async function () {
await testMove(toResource.call(this, '/path/file.txt'), toResource.call(this, '/path/file_target.txt'), true);
await testMoveOrCopy(toResource.call(this, '/path/file.txt'), toResource.call(this, '/path/file_target.txt'), true, true);
});
async function testMove(source: URI, target: URI, targetDirty?: boolean): Promise<void> {
test('copy - dirty file', async function () {
await testMoveOrCopy(toResource.call(this, '/path/file.txt'), toResource.call(this, '/path/file_target.txt'), false);
});
test('copy - dirty file (target exists and is dirty)', async function () {
await testMoveOrCopy(toResource.call(this, '/path/file.txt'), toResource.call(this, '/path/file_target.txt'), false, true);
});
async function testMoveOrCopy(source: URI, target: URI, move: boolean, targetDirty?: boolean): Promise<void> {
let sourceModel: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, source, 'utf8', undefined);
let targetModel: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, target, 'utf8', undefined);
(<TextFileEditorModelManager>accessor.textFileService.files).add(sourceModel.resource, sourceModel);
@ -162,11 +183,19 @@ suite('Files - TextFileService', () => {
assert.ok(accessor.textFileService.isDirty(targetModel.resource));
}
await accessor.textFileService.move(sourceModel.resource, targetModel.resource, true);
if (move) {
await accessor.textFileService.move(sourceModel.resource, targetModel.resource, true);
} else {
await accessor.textFileService.copy(sourceModel.resource, targetModel.resource, true);
}
assert.equal(targetModel.textEditorModel!.getValue(), 'foo');
assert.ok(!accessor.textFileService.isDirty(sourceModel.resource));
if (move) {
assert.ok(!accessor.textFileService.isDirty(sourceModel.resource));
} else {
assert.ok(accessor.textFileService.isDirty(sourceModel.resource));
}
assert.ok(accessor.textFileService.isDirty(targetModel.resource));
sourceModel.dispose();

View file

@ -1111,11 +1111,11 @@ export class TestFileService implements IFileService {
}
copy(_source: URI, _target: URI, _overwrite?: boolean): Promise<IFileStatWithMetadata> {
throw new Error('not implemented');
return Promise.resolve(null!);
}
createFile(_resource: URI, _content?: VSBuffer | VSBufferReadable, _options?: ICreateFileOptions): Promise<IFileStatWithMetadata> {
throw new Error('not implemented');
return Promise.resolve(null!);
}
createFolder(_resource: URI): Promise<IFileStatWithMetadata> {