提交 4e3f58f2 编写于 作者: J Joao Moreno

git: handle deleted conflicts

fixes #52787
上级 758f2495
......@@ -138,20 +138,22 @@ const ImageMimetypes = [
'image/bmp'
];
async function categorizeResourceByResolution(resources: Resource[]): Promise<{ merge: Resource[], resolved: Resource[], unresolved: Resource[] }> {
async function categorizeResourceByResolution(resources: Resource[]): Promise<{ merge: Resource[], resolved: Resource[], unresolved: Resource[], deletionConflicts: Resource[] }> {
const selection = resources.filter(s => s instanceof Resource) as Resource[];
const merge = selection.filter(s => s.resourceGroupType === ResourceGroupType.Merge);
const isBothAddedOrModified = (s: Resource) => s.type === Status.BOTH_MODIFIED || s.type === Status.BOTH_ADDED;
const isAnyDeleted = (s: Resource) => s.type === Status.DELETED_BY_THEM || s.type === Status.DELETED_BY_US;
const possibleUnresolved = merge.filter(isBothAddedOrModified);
const promises = possibleUnresolved.map(s => grep(s.resourceUri.fsPath, /^<{7}|^={7}|^>{7}/));
const unresolvedBothModified = await Promise.all<boolean>(promises);
const resolved = possibleUnresolved.filter((s, i) => !unresolvedBothModified[i]);
const deletionConflicts = merge.filter(s => isAnyDeleted(s));
const unresolved = [
...merge.filter(s => !isBothAddedOrModified(s)),
...merge.filter(s => !isBothAddedOrModified(s) && !isAnyDeleted(s)),
...possibleUnresolved.filter((s, i) => unresolvedBothModified[i])
];
return { merge, resolved, unresolved };
return { merge, resolved, unresolved, deletionConflicts };
}
enum PushType {
......@@ -215,7 +217,10 @@ export class CommandCenter {
right = toGitUri(resource.resourceUri, resource.resourceGroupType === ResourceGroupType.Index ? 'index' : 'wt', { submoduleOf: repository.root });
}
} else {
left = await this.getLeftResource(resource);
if (resource.type !== Status.DELETED_BY_THEM) {
left = await this.getLeftResource(resource);
}
right = await this.getRightResource(resource);
}
......@@ -242,7 +247,7 @@ export class CommandCenter {
}
if (!left) {
await commands.executeCommand<void>('vscode.open', right, opts);
await commands.executeCommand<void>('vscode.open', right, opts, title);
} else {
await commands.executeCommand<void>('vscode.diff', left, right, title, opts);
}
......@@ -310,10 +315,15 @@ export class CommandCenter {
return this.getURI(resource.resourceUri, '');
case Status.INDEX_DELETED:
case Status.DELETED_BY_THEM:
case Status.DELETED:
return this.getURI(resource.resourceUri, 'HEAD');
case Status.DELETED_BY_US:
return this.getURI(resource.resourceUri, '~3');
case Status.DELETED_BY_THEM:
return this.getURI(resource.resourceUri, '~2');
case Status.MODIFIED:
case Status.UNTRACKED:
case Status.IGNORED:
......@@ -344,13 +354,18 @@ export class CommandCenter {
switch (resource.type) {
case Status.INDEX_MODIFIED:
case Status.INDEX_RENAMED:
case Status.DELETED_BY_THEM:
return `${basename} (Index)`;
case Status.MODIFIED:
case Status.BOTH_ADDED:
case Status.BOTH_MODIFIED:
return `${basename} (Working Tree)`;
case Status.DELETED_BY_US:
return `${basename} (Theirs)`;
case Status.DELETED_BY_THEM:
return `${basename} (Ours)`;
}
return '';
......@@ -704,7 +719,7 @@ export class CommandCenter {
}
const selection = resourceStates.filter(s => s instanceof Resource) as Resource[];
const { resolved, unresolved } = await categorizeResourceByResolution(selection);
const { resolved, unresolved, deletionConflicts } = await categorizeResourceByResolution(selection);
if (unresolved.length > 0) {
const message = unresolved.length > 1
......@@ -719,6 +734,20 @@ export class CommandCenter {
}
}
try {
await this.runByRepository(deletionConflicts.map(r => r.resourceUri), async (repository, resources) => {
for (const resource of resources) {
await this._stageDeletionConflict(repository, resource);
}
});
} catch (err) {
if (/Cancelled/.test(err.message)) {
return;
}
throw err;
}
const workingTree = selection.filter(s => s.resourceGroupType === ResourceGroupType.WorkingTree);
const scmResources = [...workingTree, ...resolved, ...unresolved];
......@@ -734,7 +763,19 @@ export class CommandCenter {
@command('git.stageAll', { repository: true })
async stageAll(repository: Repository): Promise<void> {
const resources = repository.mergeGroup.resourceStates.filter(s => s instanceof Resource) as Resource[];
const { merge, unresolved } = await categorizeResourceByResolution(resources);
const { merge, unresolved, deletionConflicts } = await categorizeResourceByResolution(resources);
try {
for (const deletionConflict of deletionConflicts) {
await this._stageDeletionConflict(repository, deletionConflict.resourceUri);
}
} catch (err) {
if (/Cancelled/.test(err.message)) {
return;
}
throw err;
}
if (unresolved.length > 0) {
const message = unresolved.length > 1
......@@ -752,6 +793,41 @@ export class CommandCenter {
await repository.add([]);
}
private async _stageDeletionConflict(repository: Repository, uri: Uri): Promise<void> {
const uriString = uri.toString();
const resource = repository.mergeGroup.resourceStates.filter(r => r.resourceUri.toString() === uriString)[0];
if (!resource) {
return;
}
if (resource.type === Status.DELETED_BY_THEM) {
const keepIt = localize('keep ours', "Keep Our Version");
const deleteIt = localize('delete', "Delete File");
const result = await window.showInformationMessage(localize('deleted by them', "File '{0}' was deleted by them and modified by us.\n\nWhat would you like to do?", path.basename(uri.fsPath)), { modal: true }, keepIt, deleteIt);
if (result === keepIt) {
await repository.add([uri]);
} else if (result === deleteIt) {
await repository.rm([uri]);
} else {
throw new Error('Cancelled');
}
} else if (resource.type === Status.DELETED_BY_US) {
const keepIt = localize('keep theirs', "Keep Their Version");
const deleteIt = localize('delete', "Delete File");
const result = await window.showInformationMessage(localize('deleted by us', "File '{0}' was deleted by us and modified by them.\n\nWhat would you like to do?", path.basename(uri.fsPath)), { modal: true }, keepIt, deleteIt);
if (result === keepIt) {
await repository.add([uri]);
} else if (result === deleteIt) {
await repository.rm([uri]);
} else {
throw new Error('Cancelled');
}
}
}
@command('git.stageChange')
async stageChange(uri: Uri, changes: LineChange[], index: number): Promise<void> {
const textEditor = window.visibleTextEditors.filter(e => e.document.uri.toString() === uri.toString())[0];
......
......@@ -116,6 +116,8 @@ export class GitContentProvider {
const uriString = fileUri.toString();
const [indexStatus] = repository.indexGroup.resourceStates.filter(r => r.resourceUri.toString() === uriString);
ref = indexStatus ? '' : 'HEAD';
} else if (/^~\d$/.test(ref)) {
ref = `:${ref[1]}`;
}
try {
......
......@@ -900,6 +900,18 @@ export class Repository {
await this.run(args);
}
async rm(paths: string[]): Promise<void> {
const args = ['rm', '--'];
if (!paths || !paths.length) {
return;
}
args.push(...paths);
await this.run(args);
}
async stage(path: string, data: string): Promise<void> {
const child = this.stream(['hash-object', '--stdin', '-w', '--path', path], { stdio: [null, null, null] });
child.stdin.end(data, 'utf8');
......
......@@ -198,12 +198,14 @@ export class Resource implements SourceControlResourceState {
return 'U';
case Status.IGNORED:
return 'I';
case Status.DELETED_BY_THEM:
return 'D';
case Status.DELETED_BY_US:
return 'D';
case Status.INDEX_COPIED:
case Status.BOTH_DELETED:
case Status.ADDED_BY_US:
case Status.DELETED_BY_THEM:
case Status.ADDED_BY_THEM:
case Status.DELETED_BY_US:
case Status.BOTH_ADDED:
case Status.BOTH_MODIFIED:
return 'C';
......@@ -281,6 +283,7 @@ export const enum Operation {
Diff = 'Diff',
MergeBase = 'MergeBase',
Add = 'Add',
Remove = 'Remove',
RevertFiles = 'RevertFiles',
Commit = 'Commit',
Clean = 'Clean',
......@@ -755,6 +758,10 @@ export class Repository implements Disposable {
await this.run(Operation.Add, () => this.repository.add(resources.map(r => r.fsPath)));
}
async rm(resources: Uri[]): Promise<void> {
await this.run(Operation.Remove, () => this.repository.rm(resources.map(r => r.fsPath)));
}
async stage(resource: Uri, contents: string): Promise<void> {
const relativePath = path.relative(this.repository.root, resource.fsPath).replace(/\\/g, '/');
await this.run(Operation.Stage, () => this.repository.stage(relativePath, contents));
......
......@@ -263,16 +263,16 @@ export class MainThreadTextEditors implements MainThreadTextEditorsShape {
// --- commands
CommandsRegistry.registerCommand('_workbench.open', function (accessor: ServicesAccessor, args: [URI, IEditorOptions, EditorViewColumn]) {
CommandsRegistry.registerCommand('_workbench.open', function (accessor: ServicesAccessor, args: [URI, IEditorOptions, EditorViewColumn, string?]) {
const editorService = accessor.get(IEditorService);
const editorGroupService = accessor.get(IEditorGroupsService);
const openerService = accessor.get(IOpenerService);
const [resource, options, position] = args;
const [resource, options, position, label] = args;
if (options || typeof position === 'number') {
// use editor options or editor view column as a hint to use the editor service for opening
return editorService.openEditor({ resource, options }, viewColumnToEditorGroup(editorGroupService, position)).then(_ => void 0);
return editorService.openEditor({ resource, options, label }, viewColumnToEditorGroup(editorGroupService, position)).then(_ => void 0);
}
if (resource && resource.scheme === 'command') {
......
......@@ -77,7 +77,7 @@ CommandsRegistry.registerCommand(DiffAPICommand.ID, adjustHandler(DiffAPICommand
export class OpenAPICommand {
public static ID = 'vscode.open';
public static execute(executor: ICommandsExecutor, resource: URI, columnOrOptions?: vscode.ViewColumn | vscode.TextDocumentShowOptions): Thenable<any> {
public static execute(executor: ICommandsExecutor, resource: URI, columnOrOptions?: vscode.ViewColumn | vscode.TextDocumentShowOptions, label?: string): Thenable<any> {
let options: ITextEditorOptions;
let position: EditorViewColumn;
......@@ -93,7 +93,8 @@ export class OpenAPICommand {
return executor.executeCommand('_workbench.open', [
resource,
options,
position
position,
label
]);
}
}
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册