Skip to content

Instantly share code, notes, and snippets.

@tailot
Created May 31, 2025 22:26
Show Gist options
  • Save tailot/849d5c9109c2c8eace80f21738d31143 to your computer and use it in GitHub Desktop.
Save tailot/849d5c9109c2c8eace80f21738d31143 to your computer and use it in GitHub Desktop.
#!/bin/bash
# USAGE:
# ./cleangit.sh
#
# DESCRIPTION:
# cleangit.sh is an interactive script for cleaning and maintaining a Git repository.
# It runs a series of Git commands to optimize the repository, remove obsolete branches,
# and untracked files.
#
# PREREQUISITES:
# 1. The script must be executable:
# chmod +x cleangit.sh
# 2. The script MUST be run from the root directory of the Git repository you intend to clean.
#
# OPERATION:
# The script will guide the user through the following steps, asking for confirmation before
# performing potentially destructive operations:
#
# 1. Repository integrity check (git fsck).
# 2. Garbage Collection (git gc --prune=now --aggressive) to optimize space.
# 3. Pruning of obsolete remote-tracking branches (git remote prune <remote_name>).
# You will be asked for the remote name (default: 'origin').
# 4. Optional removal of local branches that have already been merged into the current branch (HEAD),
# excluding common protected branches (master, main, develop, etc.).
# 5. Optional and destructive removal of untracked files (git clean -fdx).
# An option to perform a dry-run (git clean -fdxn) will be offered first.
#
# WARNINGS:
# - Ensure you have committed or stashed all important changes before running the script.
# - The removal of untracked files (Step 5) is a destructive operation, and
# files will be permanently deleted. Proceed with caution.
print_header() {
echo ""
echo "---------------------------------------------------------------------"
echo " $1"
echo "---------------------------------------------------------------------"
}
run_command() {
local cmd_string="$1"
echo "-> Executing: ${cmd_string}"
eval "${cmd_string}"
local status=$?
if [ $status -ne 0 ]; then
echo "WARNING: The previous command '${cmd_string}' finished with an error status: $status."
fi
return $status
}
if ! git rev-parse --is-inside-work-tree > /dev/null 2>&1; then
echo "ERROR: This script must be run from the main directory of a Git repository."
exit 1
fi
echo "Starting Git repository cleanup in: $(pwd)"
echo "Make sure you have committed or stashed all important changes before proceeding."
read -p "Do you want to continue? (y/N): " confirm_start
if [[ ! "$confirm_start" =~ ^[Yy]$ ]]; then
echo "Operation cancelled by the user."
exit 0
fi
print_header "Step 1: Repository Integrity Check (git fsck)"
run_command "git fsck --full"
echo "If 'git fsck' reports 'dangling objects', these are usually not a problem and will be handled by 'git gc'."
echo "More serious problems may require manual intervention."
print_header "Step 2: Garbage Collection (git gc)"
echo "This process may take some time, especially for large repositories."
run_command "git gc --prune=now --aggressive"
echo "'git gc' completed."
print_header "Step 3: Pruning Obsolete Remote-Tracking Branches (git remote prune)"
default_remote="origin"
read -p "For which remote do you want to run 'prune' (e.g., 'origin', 'upstream'. Default: ${default_remote}): " remote_name
remote_name="${remote_name:-${default_remote}}"
if git remote | grep -q "^${remote_name}$"; then
echo "You can run 'git remote prune ${remote_name} --dry-run' to see what would be pruned."
read -p "Do you want to prune obsolete remote-tracking branches for '${remote_name}'? (y/N): " confirm_prune_remote
if [[ "$confirm_prune_remote" =~ ^[Yy]$ ]]; then
run_command "git remote prune ${remote_name}"
echo "Pruning for '${remote_name}' completed."
else
echo "Pruning for '${remote_name}' skipped."
fi
else
echo "WARNING: The remote '${remote_name}' was not found. Skipping this step."
fi
print_header "Step 4: Removing Merged Local Branches (Optional)"
echo "This command will attempt to remove local branches that have been fully merged"
echo "into the current branch (HEAD). Common protected branches (e.g., master, main, develop) will be ignored."
current_branch_name=$(git rev-parse --abbrev-ref HEAD)
echo "Current branch: $current_branch_name"
merged_branches_to_consider=$(git branch --merged | grep -v "^\*" | sed 's/^[ *]*//' | grep -vE "^(master|main|develop|release/.*|hotfix/.*|${current_branch_name})$")
if [ -z "$merged_branches_to_consider" ]; then
echo "No merged local branches (other than protected/current) to remove."
else
echo "The following merged local branches are candidates for removal:"
echo "$merged_branches_to_consider" | sed 's/^/ - /'
read -p "Do you want to remove these branches? (y/N): " confirm_delete_merged
if [[ "$confirm_delete_merged" =~ ^[Yy]$ ]]; then
echo "$merged_branches_to_consider" | xargs -r git branch -d
echo "Selected merged local branches removed."
else
echo "Removal of merged local branches skipped."
fi
fi
print_header "Step 5: Removing Untracked Files (git clean) - DESTRUCTIVE OPERATION (Optional)"
echo "WARNING: This operation will PERMANENTLY remove untracked files from your working directory."
echo "This includes files, directories (-d), and files normally ignored by .gitignore (-x)."
echo ""
echo "It is STRONGLY RECOMMENDED to first run 'git clean -fdxn' (dry-run) to see which files would be removed."
read -p "Do you want to run a dry-run ('git clean -fdxn') now? (y/N): " confirm_dry_run_clean
if [[ "$confirm_dry_run_clean" =~ ^[Yy]$ ]]; then
run_command "git clean -fdxn"
fi
read -p "Are you SURE you want to proceed with the permanent removal of untracked files ('git clean -fdx')? (y/N): " confirm_clean
if [[ "$confirm_clean" =~ ^[Yy]$ ]]; then
echo "Running 'git clean -fdx'..."
run_command "git clean -fdx"
echo "Untracked files removed."
else
echo "Cleaning of untracked files skipped."
fi
print_header "Cleanup Completed"
echo "The repository has been processed."
echo "Check the output for any messages or warnings."
echo "It might be a good idea to check the repository status with 'git status' and 'git log'."
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment