Repository: whatthefoo/ohshitgit Branch: master Commit: 710845eb72dd Files: 10 Total size: 14.1 KB Directory structure: gitextract_g53lba8f/ ├── .gitignore ├── .npmignore ├── README.md ├── bin/ │ └── ohshit.js ├── lib/ │ ├── git.js │ ├── project.js │ ├── questions.js │ ├── speak.js │ └── tasks.js └── package.json ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ .DS_Store node_modules/ npm-debug.log* yarn-debug.log* yarn-error.log* selenium-debug.log # Editor directories and files .idea *.suo *.ntvs* *.njsproj *.sln .vscode ================================================ FILE: .npmignore ================================================ media/ ================================================ FILE: README.md ================================================ ![Oh shit!](media/prompt.png) > A cli tool to help you unfuck your git mistake. Git is hard: screwing up is easy, and figuring out how to fix your mistakes is fucking impossible. ## Install ```bash npm install -g ohshitgit ``` ## Usage Once that's done, you can run this command inside your project's root directory: ```bash ohshit ``` That's it! :tada: ## Inspired by [Katie Sylor-Miller's](https://twitter.com/ksylor) awesome webpage [Oh Shit Git!](https://ohshitgit.com/) ## Author Leif Riksheim ([@leifriksheim](https://github.com/leifriksheim)) - [WTFoo](https://github.com/whatthefoo) ================================================ FILE: bin/ohshit.js ================================================ #!/usr/bin/env node const tasks = require("../lib/tasks"); const clear = require("clear"); const figlet = require("figlet"); const project = require("../lib/project"); const questions = require("../lib/questions"); const speak = require("../lib/speak"); if (!project.gitExist()) { speak.error( "Could not locate your git project. Try again in the root folder of your project" ); process.exit(); } async function run() { clear(); speak.normal(figlet.textSync("Oh Shit Git!")); try { const { command } = await questions.init(); tasks[command](); } catch (err) {} } run(); ================================================ FILE: lib/git.js ================================================ const git = require("simple-git")(); const ListLogSummary = require("simple-git/src/responses/ListLogSummary"); const speak = require("./speak"); module.exports = { getHistory: function() { return new Promise((resolve, reject) => { git.raw( ["reflog", "--pretty=format:%gd;%gs" + ListLogSummary.COMMIT_BOUNDARY], (err, result) => { if (!err) { const reflogs = ListLogSummary.parse(result, ";", [ "head", "message" ]); resolve(reflogs.all); } resolve([]); } ); }); }, getAllCommits(branch = null) { console.log(branch); return new Promise((resolve, reject) => { git.log(["master"], (err, commits) => { if (!err) { resolve(commits.all); } reject(err); }); }); }, getLocalCommits() { return new Promise((resolve, reject) => { git.log(["@{u}.."], (err, commits) => { if (!err) { resolve(commits.all); } reject(err); }); }); }, getCurrentBranchName() { return new Promise((resolve, reject) => { git.raw(["symbolic-ref", "--short", "HEAD"], (err, branch) => { if (!err) { resolve(branch); } reject(err); }); }); }, getLatestCommit: async function() { return new Promise((resolve, reject) => { git.log(["@{u}.."], (err, commits) => { if (!err) { resolve(commits.all[0]); } reject(err); }); }); }, getDiffFiles: function() { return new Promise((resolve, reject) => { git.status(function(err, status) { if (!err) { // array of unstaged files resolve(status.files); } reject(err); }); }); }, getLocalBranches: async function() { return new Promise((resolve, reject) => { git.branchLocal((err, branches) => { if (!err) { resolve(branches.all); } reject(err); }); }); }, resetHead: (head = "HEAD~", mode = "--soft") => { // https://stackoverflow.com/questions/49388201/git-revert-back-to-certain-commit-without-changing-history-and-creating-a-new-co git.raw(["reset", head, mode], function(err, result) { if (!err) { speak.success("Reset to " + head); } }); }, resetToCommit: function(hash) { // https://stackoverflow.com/questions/49388201/git-revert-back-to-certain-commit-without-changing-history-and-creating-a-new-co git.raw(["read-tree", "-u", "--reset", hash], function(err, result) { if (!err) { speak.success("Successfully reverted back to selected commit"); } }); }, stash() { git.raw(["stash"], function(err, result) { if (!err) { speak.success(`Stashed changes`); } }); }, stashPop() { git.raw(["stash", "pop"], function(err, result) { if (!err) { speak.success(`Applied stash`); } }); }, createBranch: branchName => { git.raw(["branch", branchName], function(err, result) { if (!err) { speak.success(`Created new branch with name ${branchName}`); } }); }, checkout: branchName => { git.raw(["checkout", branchName], function(err, result) { if (!err) { speak.success(`Checked out to ${branchName}`); } }); }, addAll: function() { git.add(["."], function(err, result) { if (!err) { speak.success("Added all files"); } }); }, add: function(files) { git.add(files, function(err, result) { if (!err) { speak.success("Added selected files"); } }); }, amend: function(message) { git.raw(["commit", "--amend", "-m", message], function(err, result) { if (!err) { speak.success("Updated commit"); } }); }, diffStaged() { git.raw(["diff", "--staged"], function(err, result) { if (!err) { speak.normal(result); } }); } }; ================================================ FILE: lib/project.js ================================================ const fs = require("fs"); const path = require("path"); module.exports = { currentDirectoryBase: () => { return path.basename(process.cwd()); }, directoryExists: filePath => { try { return fs.statSync(filePath).isDirectory(); } catch (err) { return false; } }, gitExist: () => { try { return fs.statSync(".git").isDirectory(); } catch (err) { return false; } } }; ================================================ FILE: lib/questions.js ================================================ const inquirer = require("inquirer"); module.exports = { init: () => { const questions = [ { name: "command", type: "list", message: "Select an option", choices: [ { name: "I fucked up. I need a time machine!", value: "timemachine" }, { name: "I committed and immediately realized I need to add one small change!", value: "addToCommit" }, { name: "I need to change the message on my last commit!", value: "changeCommitMessage" }, { name: "Delete everything I've done locally, and make everything up to date with the remote branch!", value: "resetToRemoteBranch" }, { name: "I accidentally committed to the wrong branch!", value: "regretCommitToBranch" }, { name: "I tried to run a diff but nothing happened?!", value: "noDiff" }, { name: "Fuck this noise, I give up.", value: "fuckEverything" } ] } ]; return inquirer.prompt(questions); }, selectHead: (heads, message) => { const question = heads.reduce( (acc, head) => { return { ...acc, choices: [...acc.choices, { name: head.message, value: head.head }] }; }, { name: "head", type: "list", message: message, choices: [] } ); return inquirer.prompt([question]); }, selectCommit: (commits, message) => { const question = commits.reduce( (acc, commit) => { return { ...acc, choices: [ ...acc.choices, { name: commit.message, value: commit.hash } ] }; }, { name: "commit", type: "list", message: message, choices: [] } ); return inquirer.prompt([question]); }, selectCommits: (commits, message) => { const question = commits.reduce( (acc, commit) => { return { ...acc, choices: [ ...acc.choices, { name: commit.message, value: commit.hash } ] }; }, { name: "selectedCommits", type: "checkbox", message: message, choices: [] } ); return inquirer.prompt([question]); }, selectBranch: branches => { const question = branches.reduce( (acc, branch) => { return { ...acc, choices: [...acc.choices, { name: branch, value: branch }] }; }, { name: "selectedBranch", type: "list", message: "Select a branch", choices: [] } ); return inquirer.prompt([question]); }, allOrIndividualFiles: () => { return inquirer.prompt([ { name: "files", type: "list", message: "Fuck up's happen! What do you want to do?", choices: [ { name: "Add all my changes to last commit!", value: "all" }, { name: "Let me add individual files to the last commit!", value: "individual" } ] } ]); }, newCommitMessage: previousMessage => { return inquirer.prompt([ { name: "commitMessage", type: "input", message: "Enter a new commit message", default: previousMessage } ]); }, newBranch: () => { return inquirer.prompt([ { name: "newBranchName", type: "input", message: "Enter a name for the new branch" } ]); }, selectFiles: files => { const question = files.reduce( (acc, file) => { return { ...acc, choices: [...acc.choices, { name: file.path, value: file.path }] }; }, { name: "selectedFiles", type: "checkbox", message: "Select the files you want to add to your last commit", choices: [] } ); return inquirer.prompt([question]); }, removeAndClone: () => { return inquirer.prompt([ { name: "fuckIt", type: "confirm", message: "Warning! This will delete your local project, and clone it again. You will loose all local changes" } ]); } }; ================================================ FILE: lib/speak.js ================================================ const chalk = require("chalk"); const ora = require("ora"); module.exports = { error: string => { return console.log(chalk.bgRed.black(string)); }, success: string => { return ora(string).succeed(); }, normal: string => { return console.log(string); } }; ================================================ FILE: lib/tasks.js ================================================ const speak = require("./speak"); const git = require("./git"); const questions = require("./questions"); const util = require("util"); const exec = util.promisify(require("child_process").exec); module.exports = { timemachine: async function() { const branch = await git.getCurrentBranchName(); const commits = await git.getAllCommits(branch); if (commits.length === 0) { speak.error("Could not find any commits in this branch"); process.exit(); } const { commit } = await questions.selectCommit( commits, "Select a place you want to go back to. Make sure you have no unsaved changes you want to keep." ); git.resetToCommit(commit); }, resetToRemoteBranch: function() { console.log("Resetting"); }, addToCommit: async function() { const { files } = await questions.allOrIndividualFiles(); const diffFiles = await git.getDiffFiles(); if (diffFiles.length === 0) { speak.success("You don't have any unsaved changes"); process.exit(); } const latestCommit = await git.getLatestCommit(); if (!latestCommit) { speak.error("You have pushed all your commits already!"); process.exit(); } const { commitMessage } = await questions.newCommitMessage( latestCommit.message ); if (files === "all") { git.addAll(); git.amend(commitMessage); } if (files === "individual") { const { selectedFiles } = await questions.selectFiles(diffFiles); if (selectedFiles.length < 1) { speak.error("You didn't choose any files to add"); process.exit(); } git.add(selectedFiles); git.amend(commitMessage); } }, changeCommitMessage: async () => { const latestCommit = await git.getLatestCommit(); if (latestCommit) { const { commitMessage } = await questions.newCommitMessage( latestCommit.message ); git.amend(commitMessage); } else { speak.error("You seem to have pushed all your commits"); } }, regretCommitToMaster: async () => { const commits = await git.getLocalCommits(); if (commits.length === 0) { speak.error("Seems like you have pushed all your commits already!"); process.exit(); } const { newBranchName } = await questions.newBranch(); git.createBranch(newBranchName); git.resetHead("--hard"); git.checkout(newBranchName); }, regretCommitToBranch: async () => { const commits = await git.getLocalCommits(); if (commits.length === 0) { speak.error("Seems like you have pushed all your commits already!"); process.exit(); } git.resetHead("--soft"); git.stash(); const branches = await git.getLocalBranches(); const { selectedBranch } = await questions.selectBranch(branches); git.checkout(selectedBranch); git.stashPop(); }, noDiff: () => { git.diffStaged(); }, fuckEverything: async () => { speak.normal("Run these commands after each other"); speak.normal("cd .."); speak.normal("sudo rm -r my-fucking-git-repo-dir"); speak.normal("git clone https://some.github.url/fucking-git-repo-dir.git"); speak.normal("cd my-fucking-git-repo-dir"); } }; ================================================ FILE: package.json ================================================ { "name": "ohshitgit", "version": "0.0.5", "description": "", "main": "index.js", "bin": { "ohshit": "bin/ohshit.js", "ohshitgit": "bin/ohshit.js" }, "scripts": { "release": "np" }, "keywords": [], "author": "", "license": "ISC", "dependencies": { "chalk": "^2.4.1", "clear": "^0.1.0", "commander": "^2.18.0", "execa": "^1.0.0", "figlet": "^1.2.0", "inquirer": "^6.2.0", "ora": "^3.0.0", "promisify-node": "^0.5.0", "simple-git": "^1.96.0" }, "devDependencies": { "np": "^3.0.4" } }