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