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
================================================

> 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"
}
}
gitextract_g53lba8f/ ├── .gitignore ├── .npmignore ├── README.md ├── bin/ │ └── ohshit.js ├── lib/ │ ├── git.js │ ├── project.js │ ├── questions.js │ ├── speak.js │ └── tasks.js └── package.json
SYMBOL INDEX (7 symbols across 2 files)
FILE: bin/ohshit.js
function run (line 18) | async function run() {
FILE: lib/git.js
method getAllCommits (line 23) | getAllCommits(branch = null) {
method getLocalCommits (line 34) | getLocalCommits() {
method getCurrentBranchName (line 44) | getCurrentBranchName() {
method stash (line 101) | stash() {
method stashPop (line 108) | stashPop() {
method diffStaged (line 150) | diffStaged() {
Condensed preview — 10 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (16K chars).
[
{
"path": ".gitignore",
"chars": 166,
"preview": ".DS_Store\nnode_modules/\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\nselenium-debug.log\n\n# Editor directories and file"
},
{
"path": ".npmignore",
"chars": 7,
"preview": "media/\n"
},
{
"path": "README.md",
"chars": 603,
"preview": "\n\n> A cli tool to help you unfuck your git mistake.\n\nGit is hard: screwing up is easy, and "
},
{
"path": "bin/ohshit.js",
"chars": 602,
"preview": "#!/usr/bin/env node\n\nconst tasks = require(\"../lib/tasks\");\nconst clear = require(\"clear\");\nconst figlet = require(\"figl"
},
{
"path": "lib/git.js",
"chars": 4044,
"preview": "const git = require(\"simple-git\")();\nconst ListLogSummary = require(\"simple-git/src/responses/ListLogSummary\");\nconst sp"
},
{
"path": "lib/project.js",
"chars": 431,
"preview": "const fs = require(\"fs\");\nconst path = require(\"path\");\n\nmodule.exports = {\n currentDirectoryBase: () => {\n return p"
},
{
"path": "lib/questions.js",
"chars": 4479,
"preview": "const inquirer = require(\"inquirer\");\n\nmodule.exports = {\n init: () => {\n const questions = [\n {\n name: "
},
{
"path": "lib/speak.js",
"chars": 280,
"preview": "const chalk = require(\"chalk\");\nconst ora = require(\"ora\");\n\nmodule.exports = {\n error: string => {\n return console."
},
{
"path": "lib/tasks.js",
"chars": 3224,
"preview": "const speak = require(\"./speak\");\nconst git = require(\"./git\");\nconst questions = require(\"./questions\");\nconst util = r"
},
{
"path": "package.json",
"chars": 565,
"preview": "{\n \"name\": \"ohshitgit\",\n \"version\": \"0.0.5\",\n \"description\": \"\",\n \"main\": \"index.js\",\n \"bin\": {\n \"ohshit\": \"bin/"
}
]
About this extraction
This page contains the full source code of the whatthefoo/ohshitgit GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 10 files (14.1 KB), approximately 3.8k tokens, and a symbol index with 7 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.