Skip to content

Instantly share code, notes, and snippets.

@viatcheslavmogilevsky
Last active October 11, 2026 17:33
Show Gist options
  • Select an option

  • Save viatcheslavmogilevsky/e45bc2788f796b90220e4a0ff3be635f to your computer and use it in GitHub Desktop.

Select an option

Save viatcheslavmogilevsky/e45bc2788f796b90220e4a0ff3be635f to your computer and use it in GitHub Desktop.
Checking locally cloned repos for smth to cleanup
#!/bin/bash -e
# Usage:
# ./local-repos-check.sh ~/directory-which-has-cloned-repos
# skipping network heavy checks:
# SKIP_GETTING_DEFAULT_BRANCH_FROM_REMOTE=1 SKIP_GETTING_REMOTE_PRUNABLE_BRANCHES=1 ./local-repos-check.sh ~/directory-which-has-cloned-repos
bgreen () { printf "\e[1m\e[32m" ; $@ ; printf "\e[0m"; }
red () { printf "\e[31m" ; $@ ; printf "\e[0m"; }
byellow () { printf "\e[33m" ; $@ ; printf "\e[0m"; }
conditionalcolored() {
if [[ "$@" == "0" ]]; then
bgreen echo "$@"
elif [[ "$@" == "N/A" ]]; then
byellow echo "$@"
elif [[ "$@" =~ ^main ]]; then
bgreen echo "$@"
else
red echo "$@"
fi
}
# # To debug particular directory/clone:
# for dir in $(find "$1" -type d -depth 1 | grep -F "dir-to-debug"); do
for dir in $(find "$1" -type d -depth 1); do
echo "---------------------"
echo "Directory: ${dir}"
# To debug:
# set -x
GIT_NON_EMPTY_STATUS="N/A"
NON_STANDARD_BRANCHES_COUNT="N/A"
CURRENT_BRANCH_IS_NON_STARDARD="N/A"
PRUNABLE_BRANCHES_COUNT="N/A"
PRUNABLE_OBJ_COUNT="N/A"
SIZE_TO_BE_CLEANED_UP="N/A"
GIT_STASH_COUNT="N/A"
unset UNAVAILABLE_REMOTES
UNAVAILABLE_REMOTES_COUNT=0
set +e
GIT_RAW_STATUS=$(git -C $dir status 2>&1)
GIT_STATUS_FAILED="${?}"
set -e
if [ "${GIT_STATUS_FAILED}" -eq 0 ]; then
# Empty/non-empty status:
set +e
echo $GIT_RAW_STATUS | grep -q -F "nothing to commit, working tree clean" > /dev/null 2>&1
GREP_FAILED="${?}"
set -e
GIT_NON_EMPTY_STATUS="0"
if [ "${GREP_FAILED}" -ne 0 ]; then
GIT_NON_EMPTY_STATUS="1"
fi
# Does it have non-standard branch:
REMOTES=$(git -C $dir remote)
DEFAULT_BRANCHES=""
if [ -z "$SKIP_GETTING_DEFAULT_BRANCH_FROM_REMOTE" ]; then
for remote in $(echo $REMOTES); do
set +e
def_branch_output=$(git -C $dir remote show $remote 2>&1)
def_branch_status="${?}"
set -e
if [ "${def_branch_status}" -eq 0 ]; then
def_branch=$(echo "$def_branch_output" | sed -n '/HEAD branch/s/.*: //p')
DEFAULT_BRANCHES="${DEFAULT_BRANCHES}|${def_branch}"
else
set +e
echo $def_branch_output | grep -q -F "ERROR: Repository not found" > /dev/null 2>&1
def_branch_output_grep_failed="${?}"
set -e
if [ "${def_branch_output_grep_failed}" -eq 0 ]; then
UNAVAILABLE_REMOTES+=("${remote}")
UNAVAILABLE_REMOTES_COUNT=$((UNAVAILABLE_REMOTES_COUNT+1))
# echo "Oops, repo is not available"
# break
else
echo "Oops, something wrong"
fi
fi
done
fi
DEFAULT_BRANCHES="^(main|master|develop|dev|staging${DEFAULT_BRANCHES})\$"
NON_STANDARD_BRANCHES=$(git -C $dir branch | sed -E 's/^( |\*)+//g' | grep -v -E "$DEFAULT_BRANCHES" || true)
if [ ! -z "$NON_STANDARD_BRANCHES" ]; then
NON_STANDARD_BRANCHES_COUNT=$(echo "$NON_STANDARD_BRANCHES" | wc -l | sed 's/^ *//')
else
NON_STANDARD_BRANCHES_COUNT=0
fi
# Whether current branch is non-standard:
CURRENT_BRANCH_IS_NON_STARDARD=$(git -C $dir branch | grep -E '^\* +' | sed -E 's/^( |\*)+//g' | grep -v -E "$DEFAULT_BRANCHES" | wc -l | sed 's/^ *//')
# Prunable branches:
PRUNABLE_BRANCHES_COUNT=0
if [ -z "$SKIP_GETTING_REMOTE_PRUNABLE_BRANCHES" ]; then
for remote in $(echo $REMOTES); do
set +e
printf "%s\n" "${UNAVAILABLE_REMOTES[@]}" | grep -q -E "^${remote}\$" > /dev/null 2>&1
unavailable_remotes_search_status="${?}"
set -e
if [ "${unavailable_remotes_search_status}" -ne 0 ]; then
set +e
prunable_branch_output=$(git -C $dir remote prune -n $remote 2>&1)
prunable_branch_status="${?}"
set -e
if [ "${prunable_branch_status}" -eq 0 ]; then
PRUNABLE_BRANCHES_IN_CURRENT_REMOTE=$(echo "$prunable_branch_output" | grep -F "[would prune]" | wc -l | sed 's/^ *//')
PRUNABLE_BRANCHES_COUNT=$(( $PRUNABLE_BRANCHES_COUNT + $PRUNABLE_BRANCHES_IN_CURRENT_REMOTE ))
else
set +e
echo $prunable_branch_output | grep -q -F "ERROR: Repository not found" > /dev/null 2>&1
prunable_branch_output_grep_failed="${?}"
set -e
if [ "${prunable_branch_output_grep_failed}" -eq 0 ]; then
UNAVAILABLE_REMOTES+=("${remote}")
UNAVAILABLE_REMOTES_COUNT=$((UNAVAILABLE_REMOTES_COUNT+1))
# echo "Oops, repo is not available"
# break
else
echo "Oops, something wrong"
fi
fi
fi
done
fi
# Prunable objects:
PRUNABLE_OBJ_COUNT=$(git -C $dir prune -n | wc -l | sed 's/^ *//')
SIZE_TO_BE_CLEANED_UP="0.0"
FILES_TO_BE_CLEANED_UP=$(git -C $dir clean --force --force -d -X --dry-run | sed 's|^Would remove ||g')
if [[ $(echo $FILES_TO_BE_CLEANED_UP | tr -d '\n' | wc -c) -ne 0 ]]; then
IGNORED_FILES_TO_KEEP=$(git -C $dir ls-files | grep -E '.example$' | sed 's|\.example$||g')
SIZE_TO_BE_CLEANED_UP=$(comm -23 <(echo "$FILES_TO_BE_CLEANED_UP") <(echo "$IGNORED_FILES_TO_KEEP") | awk -v dir="$dir" '{print dir "/" $0}' | xargs du -s -k | awk '{s+=$1} END {printf "%.1f", s/1024}')
fi
GIT_STASH_COUNT=$(git -C $dir stash list | wc -l | sed 's/^ *//')
# Worktrees:
WORKTREE_STATUS="main worktree, there is no linked worktrees"
WORKTREE_OUTPUT=$(git -C $dir worktree list | awk '{print $1}')
WORKTREE_COUNT=$(echo "$WORKTREE_OUTPUT" | wc -l)
if [ "$WORKTREE_COUNT" -ne 1 ]; then
WORKTREE_MAIN_PATH=$(echo "$WORKTREE_OUTPUT" | head -n 1)
set +e
echo "$WORKTREE_MAIN_PATH" | grep -q -F "${dir}" > /dev/null 2>&1
worktree_output_grep_failed="${?}"
set -e
if [ "${worktree_output_grep_failed}" -ne 0 ]; then
WORKTREE_STATUS="linked worktree of ${WORKTREE_MAIN_PATH}"
else
WORKTREE_COUNT=$((WORKTREE_COUNT-1))
WORKTREE_STATUS="main worktree, linked worktrees count ${WORKTREE_COUNT}"
fi
fi
fi
OVERALL_DISK_USAGE=$(du -s -k $dir | awk '{printf "%.1f\n", $1/1024}')
# To debug:
# set +x
printf "non-empty git status: "; conditionalcolored $GIT_NON_EMPTY_STATUS
printf "count of non-standard branches: "; conditionalcolored $NON_STANDARD_BRANCHES_COUNT
printf "current branch is non-standard: "; conditionalcolored $CURRENT_BRANCH_IS_NON_STARDARD
printf "count of prunable branches: "; conditionalcolored $PRUNABLE_BRANCHES_COUNT
printf "count of prunable objects: "; conditionalcolored $PRUNABLE_OBJ_COUNT
printf "size to be cleaned-up: "; echo "${SIZE_TO_BE_CLEANED_UP}"
printf "overall disk usage: "; echo "${OVERALL_DISK_USAGE}"
printf "git stash size: "; conditionalcolored $GIT_STASH_COUNT
printf "unavaliable remotes count: "; conditionalcolored $UNAVAILABLE_REMOTES_COUNT
printf "worktree status: "; conditionalcolored $WORKTREE_STATUS
done
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment