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(true))) 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; }, } ); }