Last active
April 27, 2024 08:27
-
-
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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