Git - add git blame decoration to diff editors (#234714)

* Initial implementation

* Manually fix the merge

* WIP - saving my work

* Made good progress, saving my work

* Remove debug statement
This commit is contained in:
Ladislau Szomoru
2024-11-27 07:34:34 +01:00
committed by GitHub
parent 800b152b41
commit 389cc76d7e

View File

@@ -281,33 +281,78 @@ export class GitBlameController {
return;
}
// Working tree diff information
const diffInformationWorkingTree = textEditor.diffInformation
.filter(diff => diff.original && isGitUri(diff.original))
.find(diff => fromGitUri(diff.original!).ref !== 'HEAD');
let allChanges: readonly TextEditorChange[];
let workingTreeChanges: readonly TextEditorChange[];
let workingTreeAndIndexChanges: readonly TextEditorChange[] | undefined;
// Working tree + index diff information
const diffInformationWorkingTreeAndIndex = textEditor.diffInformation
.filter(diff => diff.original && isGitUri(diff.original))
.find(diff => fromGitUri(diff.original!).ref === 'HEAD');
if (isGitUri(textEditor.document.uri)) {
const { ref } = fromGitUri(textEditor.document.uri);
// Working tree diff information is not present or it is stale
if (!diffInformationWorkingTree || diffInformationWorkingTree.isStale) {
return;
// For the following scenarios we can discard the diff information
// 1) Commit - Resource in the multi-file diff editor when viewing the details of a commit.
// 2) HEAD - Resource on the left-hand side of the diff editor when viewing a resource from the index.
// 3) ~ - Resource on the left-hand side of the diff editor when viewing a resource from the working tree.
if (/^[0-9a-f]{40}$/i.test(ref) || ref === 'HEAD' || ref === '~') {
workingTreeChanges = allChanges = [];
workingTreeAndIndexChanges = undefined;
} else if (ref === '') {
// Resource on the right-hand side of the diff editor when viewing a resource from the index.
const diffInformationWorkingTreeAndIndex = textEditor.diffInformation
.filter(diff => diff.original && isGitUri(diff.original))
.find(diff => fromGitUri(diff.original!).ref === 'HEAD');
// Working tree + index diff information is present and it is stale
if (diffInformationWorkingTreeAndIndex && diffInformationWorkingTreeAndIndex.isStale) {
return;
}
workingTreeChanges = [];
workingTreeAndIndexChanges = allChanges = diffInformationWorkingTreeAndIndex?.changes ?? [];
} else {
throw new Error(`Unexpected ref: ${ref}`);
}
} else {
// Working tree diff information
const diffInformationWorkingTree = textEditor.diffInformation
.filter(diff => diff.original && isGitUri(diff.original))
.find(diff => fromGitUri(diff.original!).ref === '');
// Working tree diff information is not present or it is stale
if (!diffInformationWorkingTree || diffInformationWorkingTree.isStale) {
return;
}
// Working tree + index diff information
const diffInformationWorkingTreeAndIndex = textEditor.diffInformation
.filter(diff => diff.original && isGitUri(diff.original))
.find(diff => fromGitUri(diff.original!).ref === 'HEAD');
// Working tree + index diff information is present and it is stale
if (diffInformationWorkingTreeAndIndex && diffInformationWorkingTreeAndIndex.isStale) {
return;
}
workingTreeChanges = diffInformationWorkingTree.changes;
workingTreeAndIndexChanges = diffInformationWorkingTreeAndIndex?.changes;
// For staged resources, we provide an additional "original resource" so that the editor
// diff information contains both the changes that are in the working tree and the changes
// that are in the working tree + index.
allChanges = workingTreeAndIndexChanges ?? workingTreeChanges;
}
// Working tree + index diff information is present and it is stale
if (diffInformationWorkingTreeAndIndex && diffInformationWorkingTreeAndIndex.isStale) {
return;
let commit: string;
if (!isGitUri(textEditor.document.uri)) {
// Resource with the `file` scheme
commit = repository.HEAD.commit;
} else {
// Resource with the `git` scheme
const { ref } = fromGitUri(textEditor.document.uri);
commit = /^[0-9a-f]{40}$/i.test(ref) ? ref : repository.HEAD.commit;
}
// For staged resources, we provide an additional "original resource" so that core can
// compute the diff information that contains the changes from the working tree and the
// index.
const diffInformation = diffInformationWorkingTreeAndIndex ?? diffInformationWorkingTree;
// Git blame information
const resourceBlameInformation = await this._getBlameInformation(textEditor.document.uri, repository.HEAD.commit);
const resourceBlameInformation = await this._getBlameInformation(textEditor.document.uri, commit);
if (!resourceBlameInformation) {
return;
}
@@ -315,19 +360,19 @@ export class GitBlameController {
const lineBlameInformation: LineBlameInformation[] = [];
for (const lineNumber of textEditor.selections.map(s => s.active.line)) {
// Check if the line is contained in the working tree diff information
if (lineRangesContainLine(diffInformationWorkingTree.changes, lineNumber + 1)) {
if (lineRangesContainLine(workingTreeChanges, lineNumber + 1)) {
lineBlameInformation.push({ lineNumber, blameInformation: l10n.t('Not Committed Yet') });
continue;
}
// Check if the line is contained in the working tree + index diff information
if (lineRangesContainLine(diffInformationWorkingTreeAndIndex?.changes ?? [], lineNumber + 1)) {
if (lineRangesContainLine(workingTreeAndIndexChanges ?? [], lineNumber + 1)) {
lineBlameInformation.push({ lineNumber, blameInformation: l10n.t('Not Committed Yet (Staged)') });
continue;
}
// Map the line number to the git blame ranges using the diff information
const lineNumberWithDiff = mapModifiedLineNumberToOriginalLineNumber(lineNumber + 1, diffInformation.changes);
const lineNumberWithDiff = mapModifiedLineNumberToOriginalLineNumber(lineNumber + 1, allChanges);
const blameInformation = resourceBlameInformation.find(blameInformation => {
return blameInformation.ranges.find(range => {
return lineNumberWithDiff >= range.startLineNumber && lineNumberWithDiff <= range.endLineNumber;
@@ -407,9 +452,15 @@ class GitBlameEditorDecoration {
editor.setDecorations(this._decorationType, []);
}
// Only support resources with `file` and `git` schemes
if (textEditor.document.uri.scheme !== 'file' && !isGitUri(textEditor.document.uri)) {
textEditor.setDecorations(this._decorationType, []);
return;
}
// Get blame information
const blameInformation = this._controller.textEditorBlameInformation.get(textEditor);
if (!blameInformation || textEditor.document.uri.scheme !== 'file') {
if (!blameInformation) {
textEditor.setDecorations(this._decorationType, []);
return;
}
@@ -507,8 +558,14 @@ class GitBlameStatusBarItem {
this._disposables.push(this._statusBarItem);
}
// Only support resources with `file` and `git` schemes
if (textEditor.document.uri.scheme !== 'file' && !isGitUri(textEditor.document.uri)) {
this._statusBarItem.hide();
return;
}
const blameInformation = this._controller.textEditorBlameInformation.get(textEditor);
if (!blameInformation || blameInformation.length === 0 || textEditor.document.uri.scheme !== 'file') {
if (!blameInformation || blameInformation.length === 0) {
this._statusBarItem.hide();
return;
}