vscode_dwm-git-simpleuse/extension.js

248 lines
7.2 KiB
JavaScript

const vscode = require("vscode");
const util = require("node:util");
const chp = require("child_process");
const execP = util.promisify(chp.exec);
let isOutPutChannelVisible = false,
logOutputChannel,
statusBarItem;
/**
* @param {vscode.ExtensionContext} context
*/
async function activate(context) {
let disposable;
logOutputChannel = makeLogOutputChannel("gitDWMSimpleUse");
statusBarItem = makeStatusBarItem();
context.subscriptions.push(
vscode.commands.registerCommand("gitDWMSimpleUse.ToggleOutputChannel", () => {
if (isOutPutChannelVisible) logOutputChannel.hide();
else logOutputChannel.show(true);
isOutPutChannelVisible = !isOutPutChannelVisible;
})
);
try {
process.chdir(vscode.workspace.workspaceFolders[0].uri.fsPath);
} catch (err) {
vscode.window.showErrorMessage(err);
}
if (await isInGitRepo()) {
statusBarItem.show();
if (!(await hasConflicts(true)))
makeSpawn("openPull", "git pull --rebase --autostash", { shell: true });
}
context.subscriptions.push(makeDocumentLinkProvider());
disposable = vscode.commands.registerCommand("dwm-git-simpleuse.fullPush", async function () {
if (
!(await isInGitRepo(true)) ||
(await hasConflicts(true)) ||
(await isWorkingTreeClean(true))
)
return;
let commitMsg = await vscode.window.showInputBox({
placeHolder: "commit message",
prompt: "choose your commit message",
value: ((await context.workspaceState.get("lastGitCommitMsg")) || "").incrementSuffixe(),
});
if (commitMsg === undefined) return;
commitMsg = commitMsg.trim();
if (!commitMsg.length) {
vscode.window.showErrorMessage("commit message needed");
return;
}
context.workspaceState.update("lastGitCommitMsg", commitMsg);
await makeSpawn("fullPush-pull", `git pull --rebase --autostash`, { shell: true });
if (await hasConflicts(true)) return;
makeSpawn(
"fullPush",
`git add . && git commit -am "${commitMsg.replace(/"/g, '\\"')}" && git push`,
{ shell: true }
);
});
context.subscriptions.push(disposable);
disposable = vscode.commands.registerCommand("dwm-git-simpleuse.initRepo", async function () {
if (await isInGitRepo()) {
vscode.window.showErrorMessage("Already in a git repository");
return;
}
let gitLink = await vscode.window.showInputBox({
placeHolder: "git repo link",
prompt: "Initialize a git repository",
value: await vscode.env.clipboard.readText(),
});
if (gitLink === undefined) return;
gitLink = gitLink.trim();
if (!gitLink.match(/^https?:\/\/\S+\.git$/).length) {
vscode.window.showErrorMessage("Link not valid");
return;
}
let gitBranch = await vscode.window.showInputBox({
placeHolder: "git branch",
prompt: "choose wich branch to use (defaults to 'main')",
value: "main",
});
if (gitBranch === undefined) return;
gitBranch = gitBranch.trim();
if (!gitBranch.length) gitBranch = "main";
makeSpawn(
"initRepo",
`git init && git remote add origin ${gitLink} && git fetch && git checkout -ft origin/${gitBranch}`,
{ shell: true }
);
});
context.subscriptions.push(disposable);
}
// This method is called when your extension is deactivated
function deactivate() {}
module.exports = {
activate,
deactivate,
};
function makeSpawn(name, command, options = {}) {
return new Promise((resolve) => {
statusBarItem.text = "$(loading~spin) GitDWMSU";
statusBarItem.tooltip = name;
let spawn = chp.spawn(`${command} 2>&1 || echo 'gitSU_error'`, options);
spawn.stdout.on("data", (d) => {
if (/\n\s*gitSU_error[\s\n]*$/.test(d.toString())) {
statusBarItem.backgroundColor = new vscode.ThemeColor("statusBarItem.errorBackground");
setTimeout(() => {
statusBarItem.backgroundColor = new vscode.ThemeColor("statusBarItem.background");
}, 5000);
vscode.window.showErrorMessage(`[${name}] An error occurred`, "Show").then((e) => {
if (e == "Show") {
logOutputChannel.show(true);
isOutPutChannelVisible = true;
}
});
logOutputChannel.error(`[${name}] ${d.toString().replace(/\n\s*gitSU_error[\s]*/, "")}`);
} else logOutputChannel.info(`[${name}] ${d.toString()}`);
});
spawn.stderr.on("data", (d) => {
logOutputChannel.error(`[${name}] ${d.toString()}`);
});
spawn.on("close", (c) => {
logOutputChannel.trace(`[${name}] child process exited with code ${c}`);
statusBarItem.text = "$(remote) GitDWMSU";
statusBarItem.tooltip = "";
resolve();
});
});
}
async function isWorkingTreeClean(doActions = false) {
const { stdout } = await execP(`git status --porcelain`);
if (doActions && stdout.trim() == "") vscode.window.showErrorMessage(`Working tree is clean`);
return stdout.trim() == "";
}
async function isInGitRepo(doActions = false) {
const { stdout } = await execP(`git rev-parse --is-inside-work-tree || echo false`);
if (doActions && stdout.trim() !== "true") vscode.window.showErrorMessage("Not a git repository");
return stdout.trim() === "true";
}
async function hasConflicts(doActions = false) {
const { stdout } = await execP(`git diff --name-only --diff-filter=U`);
if (doActions && stdout.trim() != "") {
vscode.window
.showErrorMessage(
`You have to resolve conflicts :\n${stdout.trim().replace("\n", ", ")}`,
"Show files",
"Git pull"
)
.then(async (e) => {
if (e == "Show files") {
logOutputChannel.show(true);
isOutPutChannelVisible = true;
} else if (e == "Git pull" && !(await hasConflicts(true))) {
makeSpawn("openPull", "git pull --rebase --autostash", { shell: true });
}
});
logOutputChannel.error(
"[conflicts] Conflicted files : \n" +
stdout
.split("\n")
.filter((e) => e.trim().length)
.map((e) => `F:${e}#`)
.join("\n")
);
}
return stdout.trim() != "";
}
String.prototype.incrementSuffixe = function () {
const match = this.match(/\s*\((\d+)\)\s*$/);
return match
? this.replace(new RegExp(`${match[0].escapeRegex()}$`), ` (${+match[1] + 1})`)
: this.trim().length
? this + " (1)"
: this.trim();
};
String.prototype.escapeRegex = function () {
return this.replace(/[/\-\\^$*+?.()|[\]{}]/g, "\\$&");
};
function makeLogOutputChannel(name) {
return vscode.window.createOutputChannel(name, {
log: true,
logLevel: 3,
});
}
function makeStatusBarItem() {
let s = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left, 100);
s.text = "$(remote) GitDWMSU";
s.command = "gitDWMSimpleUse.ToggleOutputChannel";
return s;
}
function makeDocumentLinkProvider() {
return vscode.languages.registerDocumentLinkProvider(
{ language: "log", scheme: "output" },
{
provideDocumentLinks(doc) {
const links = [];
const regex = /F(ile|older)?:([^#]+)#/g;
for (let line = 0; line < doc.lineCount; line++) {
let text = doc.lineAt(line).text;
let match;
while ((match = regex.exec(text)) !== null) {
const _index = match.index + 2 + (match[1] ? match[1].length : 0);
const range = new vscode.Range(
new vscode.Position(line, _index),
new vscode.Position(line, _index + match[2].length)
);
links.push(
new vscode.DocumentLink(
range,
vscode.Uri.file(
(match[1] ? "" : `${vscode.workspace.workspaceFolders[0].uri.fsPath}/`) + match[2]
)
)
);
}
}
return links;
},
}
);
}