Skip to content

Instantly share code, notes, and snippets.

@tailot
Created May 31, 2025 10:10
Show Gist options
  • Save tailot/1db3a2c49bdaf9241b82e27d2c0ea322 to your computer and use it in GitHub Desktop.
Save tailot/1db3a2c49bdaf9241b82e27d2c0ea322 to your computer and use it in GitHub Desktop.
// Usage: node removehistoryfromgit.js <path/to/your/file_to_remove.txt>
// Example: node removehistoryfromgit.js assets/images/large_confidential_file.jpg
// node removehistoryfromgit.js src/old_module.js
//
// This script completely removes a file from the Git repository's history.
// WARNING: This operation REWRITES HISTORY and is DANGEROUS.
// ALWAYS BACK UP YOUR REPOSITORY BEFORE RUNNING THIS SCRIPT.
// If the repository is shared, this will cause severe issues for collaborators
// who will need to re-clone or perform complex rebasing.
const { exec } = require('child_process');
const path = require('path');
const readline = require('readline');
const filePathArg = process.argv[2];
if (!filePathArg) {
console.error('\n❌ Error: You must specify the path of the file to remove.');
console.log('Usage: node removehistoryfromgit.js <path/relative/to/repo/root/file.ext>');
console.log('Example: node removehistoryfromgit.js src/confidential_data.txt\n');
process.exit(1);
}
const normalizedFilePath = path.normalize(filePathArg).replace(/\\/g, '/').replace(/^\/+/, '');
const command = `git filter-branch --force --index-filter 'git rm --cached --ignore-unmatch "${normalizedFilePath}"' --prune-empty --tag-name-filter cat -- --all`;
console.log(`\n--- ⚠️ CRITICAL WARNING ⚠️ ---`);
console.log(`You are about to attempt to COMPLETELY remove the file "${normalizedFilePath}" from this Git repository's history.`);
console.log(`This operation REWRITES THE REPOSITORY'S HISTORY.`);
console.log(`\n🛑 BEFORE YOU PROCEED:`);
console.log(` 1. ENSURE YOU HAVE A COMPLETE BACKUP OF THE REPOSITORY.`);
console.log(` 2. IF THIS REPOSITORY IS SHARED, NOTIFY ALL COLLABORATORS. They will need to handle the rewritten history (usually by re-cloning or performing complex rebases).`);
console.log(` 3. Understand that this operation is IRREVERSIBLE on your local repository once completed (aside from restoring from your backup).`);
console.log(`\nThe Git command that will be executed is:`);
console.log(` ${command}`);
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
rl.question('\nAre you absolutely sure you want to proceed? (yes/no): ', (answer) => {
rl.close();
if (answer.toLowerCase() !== 'yes' && answer.toLowerCase() !== 'y') {
console.log('\nOperation cancelled by the user. No changes have been made.');
process.exit(0);
}
console.log('\n🚀 Executing command... This process can take a long time for large repositories.');
console.log('Please wait for the completion or error message.\n');
const gitProcess = exec(command, (error, stdout, stderr) => {
if (error) {
console.error(`\n❌ Error during git filter-branch execution:`);
console.error(error.message);
if (stderr) {
console.error('\nError output (stderr):');
console.error(stderr);
}
if (stdout) {
console.log('\nStandard output (stdout):');
console.log(stdout);
}
console.log('\n‼️ Operation failed. The repository history has not been modified or might be in an inconsistent state.');
console.log('Check the error messages to understand the cause.');
return;
}
console.log('\n✅ --- OPERATION COMPLETED SUCCESSFULLY (locally) ---');
if (stderr) {
console.log('\nInformational output from git filter-branch (stderr):');
console.log(stderr);
}
if (stdout && stdout.trim() !== "") {
console.log('\nStandard output from git filter-branch (stdout):');
console.log(stdout);
}
console.log(`\n\n--- 🔥 IMPORTANT POST-REWRITE ACTIONS (TO BE PERFORMED MANUALLY) 🔥 ---`);
console.log(`The LOCAL history of your repository has been rewritten.`);
console.log(`The file "${normalizedFilePath}" should have been removed from the entire history.`);
console.log(`\n 1. CAREFULLY INSPECT THE REPOSITORY:`);
console.log(` - Verify that the file has actually been removed from past commits (e.g., using 'git log --all --full-history -- "${normalizedFilePath}"' which should no longer show the file, or 'git log --name-status HEAD~5' to check recent commits).`);
console.log(` - Ensure no other files have been inadvertently altered or lost.`);
console.log(`\n 2. LOCAL REPOSITORY CLEANUP (Recommended):`);
console.log(` git reflog expire --expire=now --all`);
console.log(` git gc --prune=now --aggressive`);
console.log(` (Note: 'git filter-branch' moves the original refs to 'refs/original/'. You can remove them manually if you are certain about the outcome, e.g., 'git for-each-ref --format="delete %(refname)" refs/original | git update-ref --stdin' or by deleting specific refs like 'git update-ref -d refs/original/refs/heads/main' etc.)`);
console.log(`\n 3. UPDATING THE REMOTE REPOSITORY (IF IT EXISTS):`);
console.log(` WARNING: This will overwrite history on the remote server. This is a destructive action for anyone else using the repository.`);
console.log(` git push origin --force --all`);
console.log(` git push origin --force --tags`);
console.log(` (Replace 'origin' with the name of your remote, if different).`);
console.log(`\n 4. COMMUNICATE WITH COLLABORATORS:`);
console.log(` - All collaborators will need to re-clone the repository or perform complex procedures to synchronize their local copies (e.g., fetch and forcibly rebase their branches).`);
console.log(` - It is crucial to notify them BEFORE force-pushing and coordinate the update.`);
console.log(`\nIf something went wrong and you have a backup, restore it now.`);
console.log(`If you are unsure about these steps, seek help from someone experienced with Git before proceeding with the force push.`);
});
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment