Skip to content

Instantly share code, notes, and snippets.

@ChronoMonochrome
Last active April 27, 2024 08:27
Show Gist options
  • Save ChronoMonochrome/1e477c870319f6e16b29c78fdb239966 to your computer and use it in GitHub Desktop.
Save ChronoMonochrome/1e477c870319f6e16b29c78fdb239966 to your computer and use it in GitHub Desktop.
Python script to find uncommitted / unpushed changes in git repositories managed by repo
import os
import subprocess
import xml.etree.ElementTree as ET
import traceback
def get_repo_paths_and_names(manifest_path):
"""Parses the given repo manifest file to extract repository paths and names.
Args:
manifest_path (str): Path to the repo manifest file (.xml).
Returns:
dict: A dictionary where keys are repo names and values are their paths.
"""
repos = {}
tree = ET.parse(manifest_path)
root = tree.getroot()
# Iterate through project elements for additional repositories
for project in root.findall("project"):
name = project.attrib["name"]
path = project.attrib["path"]
repos[name] = path
return repos
def check_repo_changes(repo_path):
"""Checks for uncommitted and unpushed changes in the specified Git repository.
Args:
repo_path (str): Path to the Git repository directory.
Returns:
list: A list of strings indicating the type of changes found (uncommitted, unpushed, or clean).
"""
changes = []
try:
# Check for uncommitted changes
output = subprocess.run(["git", "status", "--porcelain"], capture_output=True, check=True, cwd=repo_path)
if output.stdout.strip():
changes.append("Uncommitted changes")
# Check for unpushed changes using reflog
output = subprocess.run(["git", "reflog"], capture_output=True, check=True, cwd=repo_path)
reflog_entries = output.stdout.decode().splitlines()
# Find the first commit before the last checkout (HEAD@{N})
head_at_n = None
last_checkout_found = False
n = None
for entry in reflog_entries:
n = entry.split(" ")[0]
last_checkout_found = "checkout" in entry
if entry.startswith("HEAD@{") and last_checkout_found:
head_at_n = n
break
if not last_checkout_found and n != None:
head_at_n = n
if head_at_n:
# Check for unpushed commits since HEAD@{N}
try:
output = subprocess.run(["git", "log", f"{head_at_n}~..HEAD"], capture_output=True, check=True, cwd=repo_path)
if output.stdout.strip():
changes.append("Unpushed changes")
except subprocess.CalledProcessError:
# Handle potential errors (e.g., no commits between HEAD@{N} and HEAD)
traceback.print_exc()
pass
if not changes:
changes.append("Clean")
except subprocess.CalledProcessError:
# Handle Git command errors (e.g., not a Git repository)
changes.append("Error: Failed to check changes")
traceback.print_exc()
return changes
def main():
"""Main function to process repo manifest files, identify changes, and print results."""
default_manifest = os.path.join(".repo", "manifests", "default.xml")
local_manifests_dir = os.path.join(".repo", "local_manifests")
all_repos = {}
# Process the default manifest
all_repos.update(get_repo_paths_and_names(default_manifest))
# Process all manifest files in the local_manifests directory
for filename in os.listdir(local_manifests_dir):
if filename.endswith(".xml") and filename != "default.xml":
manifest_path = os.path.join(local_manifests_dir, filename)
all_repos.update(get_repo_paths_and_names(manifest_path))
for name, path in all_repos.items():
print(f"Repo: {name}")
if not os.path.exists(path):
print(f"Error: path {path} doesn't exist")
continue
changes = check_repo_changes(path)
if changes:
for change in changes:
print(f"\t- {change}")
else:
print("\t- Clean")
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment