#!/usr/bin/env runts -- --allow-import --allow-run --allow-read import {existsSync} from "node:fs"; import {createInterface} from "node:readline/promises"; import {stdin, stdout} from "node:process"; import {execCommand, execCommandShell, log, ProcessBar, term,} from "../libraries/deno-commons-mod.ts"; async function getGitRemoteRev(): Promise { const currentBranch = await getGitCurrentBranch(); const lsGitRemoteOrigin = await execCommand("git", [ "ls-remote", "origin", currentBranch, ]); lsGitRemoteOrigin.assertSuccess(); return lsGitRemoteOrigin.stdout.trim().split(/\s+/)[0].trim(); } async function getGitCurrentBranch(): Promise { const gitCurrentBranch = await execCommand("git", [ "branch", "--show-current", ]); gitCurrentBranch.assertSuccess(); return gitCurrentBranch.stdout.trim(); } async function getGitLocalRev(): Promise { const gitLocalRev = await execCommand("git", [ "rev-parse", "HEAD", ]); gitLocalRev.assertSuccess(); return gitLocalRev.stdout.trim(); } class GitStatusResult { newfile: string[]; modified: string[]; deleted: string[]; untracked: string[]; constructor( newfile: string[], modified: string[], deleted: string[], untracked: string[], ) { this.newfile = newfile; this.modified = modified; this.deleted = deleted; this.untracked = untracked; } } // On branch main // Your branch is up to date with 'origin/main'. // // nothing to commit, working tree clean // ---------------------------------------------------------------------- // On branch main // Your branch is up to date with 'origin/main'. // // Changes to be committed: // (use "git restore --staged ..." to unstage) // new file: single-scripts/commit.ts // ---------------------------------------------------------------------- // On branch main // Your branch is up to date with 'origin/main'. // // Changes not staged for commit: // (use "git add/rm ..." to update what will be committed) // (use "git restore ..." to discard changes in working directory) // modified: README.md // deleted: script-config.json // // Untracked files: // (use "git add ..." to include in what will be committed) // single-scripts/commit.ts // // no changes added to commit (use "git add" and/or "git commit -a") async function getGitStatus(): Promise { const newfile: string[] = []; const modified: string[] = []; const deleted: string[] = []; const untracked: string[] = []; const gitStatus = await execCommand("git", ["status"]); gitStatus.assertSuccess(); const gitStatusStdout = gitStatus.stdout.trim(); if (gitStatusStdout.includes("nothing to commit, working tree clean")) { return null; } let inChangesNotStaged = false; let inUntrackedFiles = false; gitStatusStdout.split("\n").forEach((line) => { if ( line.startsWith("Changes to be committed:") || line.startsWith("Changes not staged for commit:") ) { inChangesNotStaged = true; inUntrackedFiles = false; return; } if (line.startsWith("Untracked files:")) { inChangesNotStaged = false; inUntrackedFiles = true; return; } if (inChangesNotStaged) { const modifiedOrDeletedLine = line.trim(); if (modifiedOrDeletedLine.startsWith("new file:")) { const newFile = modifiedOrDeletedLine.substring( "new file:".length, ).trim(); newfile.push(newFile); } else if (modifiedOrDeletedLine.startsWith("modified:")) { const modifiedFile = modifiedOrDeletedLine.substring( "modified:".length, ).trim(); modified.push(modifiedFile); } else if (modifiedOrDeletedLine.startsWith("deleted:")) { const deletedFile = modifiedOrDeletedLine.substring( "deleted:".length, ).trim(); deleted.push(deletedFile); } } if (inUntrackedFiles) { const newFile = line.trim(); if (newFile && existsSync(newFile)) { untracked.push(newFile); } } }); if ( newfile.length === 0 && modified.length === 0 && deleted.length === 0 && untracked.length === 0 ) { // WILL THIS HAPPEN? return null; } return new GitStatusResult(newfile, modified, deleted, untracked); } async function checkRev(): Promise { const localRev = await getGitLocalRev(); const remoteRev = await new ProcessBar("Checking rev").call( async (): Promise => { return await getGitRemoteRev(); }, ); if (localRev === remoteRev) { console.log(term.green(`Check rev successfully, rev: ${localRev}`)); } else { throw `Check rev failed, local rev: ${localRev} vs remote rev: ${remoteRev}`; } } async function main() { await checkRev(); // check local rev <--> remote rev equals const gitStatus = await getGitStatus(); if (gitStatus === null) { log.warn("No git status found"); return; } log.info("Git status:", gitStatus); let emptyCount = 0; let message = "empty message"; const readline = createInterface({ input: stdin, output: stdout }); do { if (emptyCount++ === 3) { readline.close(); console.log("Too many empty messages, then exit"); return; } message = await readline.question("Input your commit message > "); } while (message.length == 0); readline.close(); const gitCommitArgs: string[] = []; gitCommitArgs.push("commit"); for (const file of gitStatus.modified) { gitCommitArgs.push(file); } for (const file of gitStatus.deleted) { gitCommitArgs.push(file); } for (const file of gitStatus.untracked) { gitCommitArgs.push(file); } gitCommitArgs.push("-m"); gitCommitArgs.push(message); if (gitStatus.untracked.length > 0) { const gitAddArgs = ["add"]; for (const file of gitStatus.untracked) { gitAddArgs.push(file); } log.info(">>>>>", ["git", gitAddArgs]); await execCommandShell("git", gitAddArgs); } log.info(">>>>>", ["git", gitCommitArgs]); await execCommandShell("git", gitCommitArgs); log.info(">>>>>", ["git", "push"]); await new ProcessBar("Git pushing").call(async (): Promise => { await execCommandShell("git", ["push"]); }); } main().catch((err) => { log.error(err); process.exit(0); }).then(() => process.exit(0)); // @SCRIPT-SIGNATURE-V1: yk-r1.ES256.20260207T012258+08:00.MEUCIEMd/yU+NpLUaEnUVl65 // 6GG+ZETEZ/7Sqbj1bz4BUB6bAiEAiWQZCmeq80xCSnwownqPizH8NR3uu2/q0vWm3XicbOY=