I recently realized “whoa, I have a lot of repos here” and wasn’t confident everything has been pushed up. I have a plethora of side projects, along with loads of client repos, way too many to manually go through and check.
I went ahead and created a Python script, well, ChatGPT created it for me. It iterates through and outputs a nice list view of all my repos grouped by their commit and push status!

Now I get to go through the 27 repos, committing and pushing. This is a good reminder for me to commit and push!
If you’re a serial side project developer, I hope this little script helps!
Here’s the script!
import os
import subprocess
def get_git_status(repo_path):
try:
# Get current branch
branch = subprocess.check_output(
['git', '-C', repo_path, 'rev-parse', '--abbrev-ref', 'HEAD'],
stderr=subprocess.STDOUT
).strip().decode('utf-8')
# Get git status
status = subprocess.check_output(
['git', '-C', repo_path, 'status', '--short'],
stderr=subprocess.STDOUT
).strip().decode('utf-8')
# Check if there are changes not committed
if status:
commit_status = "Changes not committed"
else:
commit_status = "All changes committed"
# Check if there are changes not pushed
status_push = subprocess.check_output(
['git', '-C', repo_path, 'status', '-sb'],
stderr=subprocess.STDOUT
).strip().decode('utf-8')
if 'ahead' in status_push:
push_status = "Changes not pushed"
else:
push_status = "All changes pushed"
return branch, commit_status, push_status
except subprocess.CalledProcessError:
return None, None, None
def iterate_git_repos(base_path):
result = []
for root, dirs, _ in os.walk(base_path):
for dir_name in dirs:
repo_path = os.path.join(root, dir_name)
if os.path.isdir(os.path.join(repo_path, '.git')):
branch, commit_status, push_status = get_git_status(repo_path)
if branch:
result.append({
'Folder': repo_path,
'Branch': branch,
'Commit Status': commit_status,
'Push Status': push_status
})
# Prevent os.walk from traversing subdirectories of directories that have .git
dirs[:] = [d for d in dirs if not os.path.isdir(os.path.join(root, d, '.git'))]
return result
def main():
base_path = '/Users/david.lozzi/git' # Replace with your base directory
repos_status = iterate_git_repos(base_path)
# Step 1: Create a dictionary for grouping
status_groups = {}
# Step 2: Populate the dictionary with groups
for repo in repos_status:
key = (repo['Commit Status'], repo['Push Status'])
if key not in status_groups:
status_groups[key] = []
status_groups[key].append({'Folder': repo['Folder'], 'Branch': repo['Branch']})
# Step 3: Display each group
for status, repos in status_groups.items():
commit_status, push_status = status
print(f"{len(repos)} Commit: {commit_status} | Push: {push_status}")
for repo in repos:
print(f" Folder: {repo['Folder']} | Branch: {repo['Branch']}")
print('---')
if __name__ == "__main__":
main()

My public repo statuses would be best represented by a tumbleweed. 😆
LikeLike