Skip to content

Commit

Permalink
Call git blame directly and parse porcelain
Browse files Browse the repository at this point in the history
  • Loading branch information
carlthome committed Sep 16, 2023
1 parent 7f5824a commit 53c8d1c
Showing 1 changed file with 73 additions and 27 deletions.
100 changes: 73 additions & 27 deletions src/extension.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,44 @@
import * as vscode from "vscode";
import { GitExtension, Repository, API } from "./git";
import * as cp from "child_process";

function parseGitBlamePorcelain(blame: string): { [key: string]: string } {
const fields = Object.fromEntries(
blame
.trim()
.split("\n")
.map((line: string) => {
const words = line.split(" ");
const key = words[0];
const value = words.slice(1).join(" ");
return [key, value];
})
);
return fields;
}

function relativeTimePassed(now: number, past: number): string {
var msMinutes = 60 * 1000;
var msHours = msMinutes * 60;
var msDays = msHours * 24;
var msMonths = msDays * 30;
var msYears = msDays * 365;

var diff = now - past;

if (diff < msMinutes) {
return Math.round(diff / 1000) + " seconds ago";
} else if (diff < msHours) {
return Math.round(diff / msMinutes) + " minutes ago";
} else if (diff < msDays) {
return Math.round(diff / msHours) + " hours ago";
} else if (diff < msMonths) {
return "Around " + Math.round(diff / msDays) + " days ago";
} else if (diff < msYears) {
return "Around " + Math.round(diff / msMonths) + " months ago";
} else {
return "Around " + Math.round(diff / msYears) + " years ago";
}
}

const annotationDecoration: vscode.TextEditorDecorationType =
vscode.window.createTextEditorDecorationType({
Expand All @@ -25,33 +64,37 @@ export function activate(context: vscode.ExtensionContext) {
activeEditor.selection.active.line
);

const gitExtension =
vscode.extensions.getExtension<GitExtension>("vscode.git");
if (gitExtension === undefined) {
vscode.window.showErrorMessage("Unable to load git extension");
return;
}
const git = gitExtension.exports.getAPI(1);
const repo = git.getRepository(activeEditor.document.uri);
const file = activeEditor.document.uri;
const line = activeLine.lineNumber;
const command = "git";
const args = ["blame", "--porcelain", `-L${line},+1`, file.fsPath];
const workspaceFolder =
vscode.workspace.getWorkspaceFolder(file)?.uri.path;
const options = { cwd: workspaceFolder };
const cmd = cp.spawn(command, args, options);

if (repo === null) {
vscode.window.showErrorMessage("Unable to get git repository");
return;
}
cmd.stdout.on("data", (data) => {
const blame = data.toString();

const fields = parseGitBlamePorcelain(blame);

repo.blame(activeEditor.document.uri.path).then((blame) => {
const range = activeEditor.document.validateRange(
new vscode.Range(
activeLine.lineNumber,
activeLine.text.length,
activeLine.lineNumber,
0
)
const range = new vscode.Range(
activeLine.lineNumber,
activeLine.text.length,
activeLine.lineNumber,
0
);

// TODO Pretty format and only use blame for selected line.
// activeLine.text
const message = blame;
const elapsed = relativeTimePassed(
Date.now(),
parseInt(fields["author-time"])
);

// TODO If same username, write "You" instead of username.
// TODO If dirty, set summary to "Uncommitted changes".
// TODO If close in time, set summary to "now".
const message = `${fields.author}, ${elapsed}${fields.summary}`;
const hoverMessage = JSON.stringify(fields, null, 2);

const renderOptions = {
after: {
Expand All @@ -60,12 +103,16 @@ export function activate(context: vscode.ExtensionContext) {
contentText: message,
fontWeight: "normal",
fontStyle: "normal",
textDecoration: "none; opacity: 0.5; position: absolute;",
textDecoration: "none; opacity: 0.25; position: absolute;",
},
};

const decorations = [
{ range: range, hoverMessage: message, renderOptions: renderOptions },
{
range: range,
hoverMessage: hoverMessage,
renderOptions: renderOptions,
},
];

activeEditor.setDecorations(annotationDecoration, decorations);
Expand All @@ -74,5 +121,4 @@ export function activate(context: vscode.ExtensionContext) {
);
}

// This method is called when your extension is deactivated
export function deactivate() {}

0 comments on commit 53c8d1c

Please sign in to comment.