Skip to content

Instantly share code, notes, and snippets.

@Johnpc123
Last active June 26, 2025 14:59
Show Gist options
  • Select an option

  • Save Johnpc123/41fc897ef53d79190cc191db5b6b472d to your computer and use it in GitHub Desktop.

Select an option

Save Johnpc123/41fc897ef53d79190cc191db5b6b472d to your computer and use it in GitHub Desktop.
Purges all git history of a folder, but keeps its current state in the latest commit. Used for clearing accidentaly commiting unencrypted secrets.
#!/bin/bash
# purge-git-path-history.sh
# Purges all git history of a file or folder, but keeps its current state in the latest commit.
# Useful for when you've accidentally commited sensitive or confidential information and must purge it from commmit history
# with post-operation guidance, safety checks, and logging.
set -e
usage() {
echo "Usage: $0 <relative-folder-path> [--dry-run]"
echo
echo "This script will:"
echo " - Remove all history of <relative-folder-path> from your git repository."
echo " - Restore the current contents of the folder in a new commit."
echo
echo "WARNING:"
echo " - This operation rewrites git history and is DESTRUCTIVE."
echo " - You should make a backup of your repository before proceeding."
echo " - All collaborators will need to re-clone or reset after this change."
echo " - You must have git-filter-repo installed."
echo
echo "Options:"
echo " --dry-run Only print what would be done, do not perform any actions."
echo
echo "Example:"
echo " $0 myfolder"
exit 1
}
if [ $# -lt 1 ]; then
usage
fi
FOLDER="$1"
DRY_RUN=0
LOG_FILE="purge-git-folder-history.log"
if [[ "$2" == "--dry-run" ]]; then
DRY_RUN=1
fi
if [[ "$FOLDER" == *".."* ]]; then
echo "Error: Invalid path component '..' found in '$FOLDER'"
exit 2
fi
if [ ! -d "$FOLDER" ]; then
echo "Error: Folder '$FOLDER' does not exist in the current directory."
exit 3
fi
if ! git rev-parse --is-inside-work-tree > /dev/null 2>&1; then
echo "Error: This script must be run from the root of a git repository."
exit 4
fi
if ! command -v git-filter-repo > /dev/null 2>&1; then
echo "Error: git-filter-repo is not installed."
echo "Install with: pip install git-filter-repo"
exit 5
fi
# Check for uncommitted changes
if ! git diff-index --quiet HEAD --; then
echo "Warning: You have uncommitted changes. Please commit or stash them before running this script."
exit 11
fi
# Check if 'origin' remote exists before filtering
ORIGIN_URL=$(git remote get-url origin 2>/dev/null || echo "")
# Print backup locations
REPO_BACKUP="../$(basename "$PWD")-backup"
FOLDER_BACKUP="/tmp/$(basename "$FOLDER")-backup"
# Warn if backup folder exists
if [ -e "$FOLDER_BACKUP" ]; then
echo "Warning: Backup path $FOLDER_BACKUP already exists. Aborting to prevent overwrite."
exit 12
fi
# Confirm script is being run from repo root
REPO_ROOT=$(git rev-parse --show-toplevel)
if [ "$PWD" != "$REPO_ROOT" ]; then
echo "Warning: It is recommended to run this script from the root of your git repository ($REPO_ROOT)."
read -p "Continue anyway? (yes/NO): " CONFIRM_DIR
if [[ "$CONFIRM_DIR" != "yes" ]]; then
echo "Aborted."
exit 13
fi
fi
# Confirmation prompt
echo "WARNING: This will rewrite git history and cannot be undone without a backup."
echo "Backups will be made at:"
echo " Repo: $REPO_BACKUP"
echo " Folder: $FOLDER_BACKUP"
read -p "Are you sure you want to continue? (yes/NO): " CONFIRM
if [[ "$CONFIRM" != "yes" ]]; then
echo "Aborted."
exit 10
fi
# Dry run mode
if [ $DRY_RUN -eq 1 ]; then
echo "[DRY RUN] Would back up repo to: $REPO_BACKUP"
echo "[DRY RUN] Would back up folder to: $FOLDER_BACKUP"
echo "[DRY RUN] Would run: git filter-repo --path \"$FOLDER\" --invert-paths"
echo "[DRY RUN] Would restore folder and commit."
exit 0
fi
# Start logging
exec > >(tee -a "$LOG_FILE") 2>&1
echo "==> Backing up current repository to $REPO_BACKUP"
cd ..
cp -r "$(basename "$OLDPWD")" "$(basename "$OLDPWD")-backup"
cd "$OLDPWD"
echo "==> Backing up folder '$FOLDER' to $FOLDER_BACKUP"
cp -r "$FOLDER" "$FOLDER_BACKUP"
# Branch name check
CURRENT_BRANCH=$(git symbolic-ref --short HEAD)
if [ "$CURRENT_BRANCH" != "main" ] && [ "$CURRENT_BRANCH" != "master" ]; then
echo "Notice: You are on branch '$CURRENT_BRANCH', not 'main' or 'master'."
read -p "Proceed with branch '$CURRENT_BRANCH'? (yes/NO): " CONFIRM_BRANCH
if [[ "$CONFIRM_BRANCH" != "yes" ]]; then
echo "Aborted."
exit 14
fi
fi
echo "==> Purging history of '$FOLDER' from git repository..."
git filter-repo --path "$FOLDER" --invert-paths
echo "==> Restoring folder '$FOLDER' to working directory"
cp -r "$FOLDER_BACKUP" "$FOLDER"
git add "$FOLDER"
git commit -m "Restore $FOLDER after purging its history"
echo
echo "==> DONE."
echo "You have purged all history of '$FOLDER' and restored its current state in a new commit."
echo
# Post-operation guidance for remotes and tracking
HAS_ORIGIN=$(git remote | grep '^origin$' || true)
if [ -z "$HAS_ORIGIN" ]; then
echo "NOTICE: The 'origin' remote was removed by git-filter-repo for safety."
if [ -n "$ORIGIN_URL" ]; then
echo "To re-add it, run:"
echo " git remote add origin $ORIGIN_URL"
else
echo "To re-add your remote, run:"
echo " git remote add origin <your-repo-url>"
fi
echo
echo "After re-adding the remote, set the upstream tracking branch with:"
echo " git branch --set-upstream-to=origin/$CURRENT_BRANCH $CURRENT_BRANCH"
echo
echo "Then, force-push your changes with:"
echo " git push --force origin $CURRENT_BRANCH"
echo
echo "Replace <your-repo-url> and <$CURRENT_BRANCH> with your actual remote URL and branch name."
else
UPSTREAM=$(git rev-parse --abbrev-ref "$CURRENT_BRANCH@{upstream}" 2>/dev/null || true)
if [ -z "$UPSTREAM" ]; then
echo "NOTICE: Your local branch '$CURRENT_BRANCH' is not set to track a remote branch."
echo "To set tracking, run:"
echo " git branch --set-upstream-to=origin/$CURRENT_BRANCH $CURRENT_BRANCH"
echo
fi
echo "To force-push your rewritten history, run:"
echo " git push --force origin $CURRENT_BRANCH"
echo
fi
echo "All collaborators will need to re-clone or hard reset after this change."
# Post-operation summary
echo
echo "Summary:"
echo " - Purged history for folder: $FOLDER"
echo " - Repo backup: $REPO_BACKUP"
echo " - Folder backup: $FOLDER_BACKUP"
echo " - Log file: $LOG_FILE"
echo " - Current branch: $CURRENT_BRANCH"
echo " - Next steps: review changes, re-add remote if needed, set tracking, and force-push."
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment