Skip to content
This repository was archived by the owner on Feb 15, 2025. It is now read-only.

feat: add git blame fetch at the opening of a text file #76

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions src/extension.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as vscode from "vscode";
import * as cp from "child_process";
import { throttle } from "throttle-debounce";
import { fetchFileBlame, CommitInfo } from "./git";

import {
parseGitBlamePorcelain,
Expand All @@ -15,11 +16,14 @@
},
});

const FileCommits: Map<string, CommitInfo[]> = new Map();

Check warning on line 19 in src/extension.ts

View workflow job for this annotation

GitHub Actions / test (macos-latest)

Variable name `FileCommits` must match one of the following formats: camelCase, UPPER_CASE

Check warning on line 19 in src/extension.ts

View workflow job for this annotation

GitHub Actions / test (ubuntu-latest)

Variable name `FileCommits` must match one of the following formats: camelCase, UPPER_CASE

Check warning on line 19 in src/extension.ts

View workflow job for this annotation

GitHub Actions / test (windows-latest)

Variable name `FileCommits` must match one of the following formats: camelCase, UPPER_CASE

function showDecoration(e: { readonly textEditor: vscode.TextEditor }) {
const editor = e.textEditor;
const document = editor.document;
const activeLine = document.lineAt(editor.selection.active.line);
const { uri: file, isDirty } = document;


const command = "git";
const n = activeLine.lineNumber;
Expand All @@ -29,6 +33,17 @@
args.push("--content", "-");
}


const commitInfos = FileCommits.get(file.fsPath);
if (commitInfos !== undefined) {
const thiscommitInfo = commitInfos.find(info => info.lineNumber === n);
// TODO: get values of fields from thiscommitInfo

}
else {
console.log(`No pre-fetched commit information found for ${file.fsPath}`);
// TODO: move obsolete logic from down below here if the commitInfos is not found
}
const workspaceFolder = vscode.workspace.getWorkspaceFolder(file);
const workspaceFolderPath = workspaceFolder?.uri.fsPath;
const options = { cwd: workspaceFolderPath };
Expand Down Expand Up @@ -73,12 +88,41 @@
});
}

function showFileBlame(e: { readonly textEditor: vscode.TextEditor }) {

const editor = e.textEditor;
const document = editor.document;
const { uri: file, isDirty } = document;

fetchFileBlame(file.fsPath)
.then(commitInfos => {
console.log(`Commit Information for ${file.fsPath}`);
console.log(commitInfos);
FileCommits.set(file.fsPath, commitInfos);
})

Check warning on line 102 in src/extension.ts

View workflow job for this annotation

GitHub Actions / test (macos-latest)

Missing semicolon

Check warning on line 102 in src/extension.ts

View workflow job for this annotation

GitHub Actions / test (ubuntu-latest)

Missing semicolon

Check warning on line 102 in src/extension.ts

View workflow job for this annotation

GitHub Actions / test (windows-latest)

Missing semicolon

}
export function activate(context: vscode.ExtensionContext) {
console.log('Extension "git-line-blame" has activated.');
let showDecorationThrottled = throttle(100, showDecoration);
context.subscriptions.push(
vscode.window.onDidChangeTextEditorSelection(showDecorationThrottled),
vscode.window.onDidChangeTextEditorVisibleRanges(showDecorationThrottled),
vscode.window.onDidChangeActiveTextEditor((e) => {
const editor = vscode.window.activeTextEditor;
if (editor !== undefined && e === editor) {
showFileBlame({ textEditor: editor })

Check warning on line 114 in src/extension.ts

View workflow job for this annotation

GitHub Actions / test (macos-latest)

Missing semicolon

Check warning on line 114 in src/extension.ts

View workflow job for this annotation

GitHub Actions / test (ubuntu-latest)

Missing semicolon

Check warning on line 114 in src/extension.ts

View workflow job for this annotation

GitHub Actions / test (windows-latest)

Missing semicolon
}
}),
vscode.window.onDidChangeVisibleTextEditors(editors => {
const closedEditors = vscode.window.visibleTextEditors.filter(editor =>
!editors.includes(editor)
);
closedEditors.forEach(closedEditor => {
console.log(`Closed file: ${closedEditor.document.fileName}`);
FileCommits.delete(closedEditor.document.fileName);
});
}),
vscode.workspace.onDidSaveTextDocument((e) => {
const editor = vscode.window.activeTextEditor;
if (editor !== undefined && e === editor.document) {
Expand Down
93 changes: 93 additions & 0 deletions src/git.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import { exec } from 'child_process';

export interface CommitInfo {
commitHash: string;
author: string;
lineNumber: number;
commitTitle: string;
}

export function fetchFileBlame(file: string): Promise<CommitInfo[]> {
return new Promise<CommitInfo[]>((resolve, reject) => {
// Run git blame
exec(`git blame -w ${file}`, async (error, stdout, stderr) => {
if (error) {
console.error(`Error executing git blame: ${error.message}`);
reject(error.message);
return;
}

const commitInfos: CommitInfo[] = [];
const commitHashes = new Set<string>();

stdout.split('\n').forEach(line => {
if (line.trim() === '') {
return;
}

const match = line.match(/^([^\s]+) \(([^)]+)\s+(\d+)\) .*/);
if (match) {
const commitHash = match[1];
const authorInfo = match[2];
const lineNumber = parseInt(match[3], 10);

// Extract the author's name from authorInfo
const authorName = authorInfo.split(' ').slice(0, -3).join(' ');

commitHashes.add(commitHash);

commitInfos.push({
commitHash,
author: authorName,
lineNumber,
commitTitle: '', // Placeholder for commit title
});
} else {
console.warn(`Unexpected format for line: ${line}`);
}
});

// Fetch commit titles for the found commit hashes
try {
const commitTitles = await fetchCommitTitles(Array.from(commitHashes));

// Assign commit titles to commitInfos
commitInfos.forEach(info => {
if (commitTitles.has(info.commitHash)) {
info.commitTitle = commitTitles.get(info.commitHash) || '';
}
});

resolve(commitInfos);
} catch (fetchError) {
reject(`Error fetching commit titles: ${fetchError}`);
}
});
});
}

// Function to fetch commit titles from commit hashes using git log
function fetchCommitTitles(commitHashes: string[]): Promise<Map<string, string>> {
return new Promise<Map<string, string>>((resolve, reject) => {
const gitLogCommand = `git log --format="%H %s" ${commitHashes.join(' ')}`;

exec(gitLogCommand, (error, stdout, stderr) => {
if (error) {
reject(`Error executing git log: ${error.message}`);
return;
}

const commitTitles = new Map<string, string>();

stdout.split('\n').forEach(line => {
if (line.trim() !== '') {
const [commitHash, ...titleParts] = line.trim().split(' ');
const commitTitle = titleParts.join(' ');
commitTitles.set(commitHash, commitTitle);
}
});

resolve(commitTitles);
});
});
}