You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 

214 lines
5.4 KiB

'use strict';
import { env, SourceControlResourceState, Uri, window } from 'vscode';
import {
command,
Command,
CommandContext,
Commands,
getRepoPathOrPrompt,
isCommandContextViewNodeHasFileCommit,
isCommandContextViewNodeHasFileRefs,
} from './common';
import { Container } from '../container';
import { GitRevision } from '../git/git';
import { GitUri } from '../git/gitUri';
import { Logger } from '../logger';
import { Messages } from '../messages';
import { Arrays } from '../system';
enum Status {
INDEX_MODIFIED,
INDEX_ADDED,
INDEX_DELETED,
INDEX_RENAMED,
INDEX_COPIED,
MODIFIED,
DELETED,
UNTRACKED,
IGNORED,
ADDED_BY_US,
ADDED_BY_THEM,
DELETED_BY_US,
DELETED_BY_THEM,
BOTH_ADDED,
BOTH_DELETED,
BOTH_MODIFIED,
}
enum ResourceGroupType {
Merge,
Index,
WorkingTree,
}
interface Resource extends SourceControlResourceState {
readonly resourceGroupType: ResourceGroupType;
readonly type: Status;
}
interface ExternalDiffFile {
uri: Uri;
staged: boolean;
ref1?: string;
ref2?: string;
}
export interface ExternalDiffCommandArgs {
files?: ExternalDiffFile[];
}
@command()
export class ExternalDiffCommand extends Command {
constructor() {
super([Commands.ExternalDiff, Commands.ExternalDiffAll]);
}
protected async preExecute(context: CommandContext, args?: ExternalDiffCommandArgs) {
args = { ...args };
if (isCommandContextViewNodeHasFileCommit(context)) {
const ref1 = GitRevision.isUncommitted(context.node.commit.previousFileSha)
? ''
: context.node.commit.previousFileSha;
const ref2 = context.node.commit.isUncommitted ? '' : context.node.commit.sha;
args.files = [
{
uri: GitUri.fromFile(context.node.file, context.node.file.repoPath ?? context.node.repoPath),
staged: context.node.commit.isUncommittedStaged || context.node.file.indexStatus != null,
ref1: ref1,
ref2: ref2,
},
];
return this.execute(args);
}
if (isCommandContextViewNodeHasFileRefs(context)) {
args.files = [
{
uri: GitUri.fromFile(context.node.file, context.node.file.repoPath ?? context.node.repoPath),
staged: context.node.file.indexStatus != null,
ref1: context.node.ref1,
ref2: context.node.ref2,
},
];
return this.execute(args);
}
if (args.files == null) {
if (context.type === 'scm-states') {
args.files = context.scmResourceStates.map(r => ({
uri: r.resourceUri,
staged: (r as Resource).resourceGroupType === ResourceGroupType.Index,
}));
} else if (context.type === 'scm-groups') {
args.files = Arrays.filterMap(context.scmResourceGroups[0].resourceStates, r =>
this.isModified(r)
? {
uri: r.resourceUri,
staged: (r as Resource).resourceGroupType === ResourceGroupType.Index,
}
: undefined,
);
}
}
if (context.command === Commands.ExternalDiffAll) {
if (args.files == null) {
const repoPath = await getRepoPathOrPrompt('Open All Changes (difftool)');
if (!repoPath) return undefined;
const status = await Container.git.getStatusForRepo(repoPath);
if (status == null) {
return window.showInformationMessage("The repository doesn't have any changes");
}
args.files = [];
for (const file of status.files) {
if (file.indexStatus === 'M') {
args.files.push({ uri: file.uri, staged: true });
}
if (file.workingTreeStatus === 'M') {
args.files.push({ uri: file.uri, staged: false });
}
}
}
}
return this.execute(args);
}
private isModified(resource: SourceControlResourceState) {
const status = (resource as Resource).type;
return status === Status.BOTH_MODIFIED || status === Status.INDEX_MODIFIED || status === Status.MODIFIED;
}
async execute(args?: ExternalDiffCommandArgs) {
args = { ...args };
try {
let repoPath;
if (args.files == null) {
const editor = window.activeTextEditor;
if (editor == null) return;
repoPath = await Container.git.getRepoPathOrActive(undefined, editor);
if (!repoPath) return;
const uri = editor.document.uri;
const status = await Container.git.getStatusForFile(repoPath, uri.fsPath);
if (status == null) {
void window.showInformationMessage("The current file doesn't have any changes");
return;
}
args.files = [];
if (status.indexStatus === 'M') {
args.files.push({ uri: status.uri, staged: true });
}
if (status.workingTreeStatus === 'M') {
args.files.push({ uri: status.uri, staged: false });
}
} else {
repoPath = await Container.git.getRepoPath(args.files[0].uri.fsPath);
if (!repoPath) return;
}
const tool = Container.config.advanced.externalDiffTool || (await Container.git.getDiffTool(repoPath));
if (!tool) {
const viewDocs = 'View Git Docs';
const result = await window.showWarningMessage(
'Unable to open changes because no Git diff tool is configured',
viewDocs,
);
if (result === viewDocs) {
void env.openExternal(
Uri.parse('https://git-scm.com/docs/git-config#Documentation/git-config.txt-difftool'),
);
}
return;
}
for (const file of args.files) {
void Container.git.openDiffTool(repoPath, file.uri, {
ref1: file.ref1,
ref2: file.ref2,
staged: file.staged,
tool: tool,
});
}
} catch (ex) {
Logger.error(ex, 'ExternalDiffCommand');
void Messages.showGenericErrorMessage('Unable to open changes in diff tool');
}
}
}