Skip to content

Instantly share code, notes, and snippets.

@tigerhawkvok
Last active July 1, 2021 19:25
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tigerhawkvok/2161ef3aead495420fc84588ec50b6a3 to your computer and use it in GitHub Desktop.
Save tigerhawkvok/2161ef3aead495420fc84588ec50b6a3 to your computer and use it in GitHub Desktop.
Clean up a directory of Git projects
#!python3
"""
Cleanup all top level git directories here.
Creates a powershell or bash script to then execute
https://gist.github.com/tigerhawkvok/2161ef3aead495420fc84588ec50b6a3
Python 3.7+
"""
#pylint: disable= line-too-long, invalid-name
import os
import platform
import glob
import stat
# While convenient, unless you completely trust the
# directory names to not cause the shell script to be
# dangerously funky, leave this as False and do a quick
# sanity check first
EXECUTE_SCRIPT_AFTER_CREATION = False
# Cleans up aborted pack detritus.
# Do NOT set to True if you're cleaning on another process.
REMOVE_IN_PROGRESS_PACK_OBJECTS = True
# Remove stale LFS objects
CLEANUP_LFS = True
### No need to edit below here ###
fileExt = "ps1" if platform.system().lower() == "windows" else "sh"
feedbackStart = 'echo "`n' if platform.system().lower() == "windows" else r'printf "\n'
WRITE_FILE = f"gitCleanupScript.{fileExt}"
dirs = list()
removeList = list()
for maybeDir in glob.glob("*"):
# Check all directories at top level
if os.path.isdir(maybeDir) and (os.path.isdir(os.path.join(maybeDir, ".git")) or os.path.isfile(os.path.join(maybeDir, "HEAD"))):
# Only evaluate a directory if it has a ".git" folder
# or a "HEAD" file object (for bare repos)
dirs.append(maybeDir)
if REMOVE_IN_PROGRESS_PACK_OBJECTS:
# If there was a bad pack for whatever reason, clean them up
sub = ".git" if os.path.isdir(os.path.join(maybeDir, ".git")) else "."
searchDir = os.path.join(maybeDir, sub, "objects", "pack")
removed = 0
for abortedPackFile in glob.glob(os.path.join(searchDir, ".tmp-*pack*")):
removeList.append(abortedPackFile)
removed += 1
if removed > 0:
print(f"Going to remove {removed} abortive pack files from `{maybeDir}`...")
if len(dirs) == 0:
raise FileNotFoundError("No git directories found!")
failedRemove = 0
for abortedPackFile in removeList:
try:
os.unlink(abortedPackFile)
except OSError:
try:
# In case of a permission error...
os.chmod(abortedPackFile, 0o0777)
os.unlink(abortedPackFile)
except OSError:
failedRemove += 1
if failedRemove > 0:
print(f"Failed to remove {failedRemove} abortive pack files while enumerating directories")
if os.path.exists(WRITE_FILE):
os.unlink(WRITE_FILE)
lfsOp = "\ngit lfs prune" if CLEANUP_LFS else ""
for i, path in enumerate(dirs, 1):
opStr = f"cd '{path}'\ngit reflog expire --all --expire=now\ngit gc --prune=now --aggressive{lfsOp}\ncd ..\n"
with open(WRITE_FILE, "a") as fh:
if fileExt == "sh" and i == 1:
fh.write("#!/bin/sh\n")
fh.write(f'{feedbackStart}### Starting directory \'{path}\', {i} of {len(dirs)}... ###"\n')
fh.write(opStr)
fh.write("\n")
if fileExt == "sh":
with open(WRITE_FILE) as fd:
mode = os.fstat(fd.fileno()).st_mode
mode |= stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
os.fchmod(fd.fileno(), stat.S_IMODE(mode))
print(f"Ready to run {WRITE_FILE} ({len(dirs)} directories found)")
if EXECUTE_SCRIPT_AFTER_CREATION:
print("Executing...")
os.system(WRITE_FILE)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment