"""Search for MRs and issues related to a list of commits.""" import argparse import json import sys import subprocess import re def find_cherry_pick_source(commit_hash: str): """ For a given commit hash, find the original commit it was cherry-picked from. Args: commit_hash: The commit hash to inspect. Returns: The full hash of the original commit if found, otherwise None. """ try: # Use 'git show' to get the full commit message for the given hash. # The '-s' flag suppresses the diff output. # The '--format=%B' flag prints only the raw commit body/message. commit_message = subprocess.check_output( ["git", "show", "-s", "--format=%B", commit_hash.strip()], text=True, stderr=subprocess.PIPE, ).strip() # This regex looks for the specific line Git adds during a cherry-pick. # It captures the full 40-character SHA-1 hash. cherry_pick_pattern = re.compile( r"\(cherry picked from commit ([a-f0-9]{40})\)" ) # Search the entire commit message for the pattern. match = cherry_pick_pattern.search(commit_message) if match: # If a match is found, return the captured group (the original commit hash). return match.group(1) else: return None except subprocess.CalledProcessError as e: # This error occurs if the git command fails, e.g., for an invalid hash. print( f"Error processing commit '{commit_hash.strip()}': {e.stderr.strip()}", file=sys.stderr, ) return None except FileNotFoundError: # This error occurs if the 'git' command itself isn't found. print( "Error: 'git' command not found. Please ensure Git is installed and in your PATH.", file=sys.stderr, ) sys.exit(1) def main(): """ Main function to read commit hashes from stdin and process them. """ parser = argparse.ArgumentParser( description="A script to download all MRs from GitLab matching specified criteria." ) parser.add_argument( "--merge_requests_file", type=str, required=True, help="JSON file containing all the merge request information extracted via the GitLab API.", ) # E.g. git log --pretty=%H 3e819d83bf52abda16bb53565f6801df40d071f1..3.4.1 parser.add_argument( "--commits", required=True, help="List of commits, '-' for stdin.", ) args = parser.parse_args() mrs = [] with open(args.merge_requests_file, "r") as file: mrs = json.load(file) mrs_by_commit = {} if args.commits == "-": commit_hashes = sys.stdin.readlines() else: with open(args.commits, "r") as file: commit_hashes = file.readlines() # Arrange commits by SHA. for mr in mrs: for key in ["sha", "merge_commit_sha", "squash_commit_sha"]: sha = mr[key] if sha: mrs_by_commit[sha] = mr # Find the MRs and issues related to each commit. info = {} for sha in commit_hashes: sha = sha.strip() if not sha: continue # If a cherry-pick, extract the original hash. sha = find_cherry_pick_source(sha) or sha mr = mrs_by_commit.get(sha) commit_info = {} if mr: commit_info["merge_request"] = mr["iid"] commit_info["related_issues"] = [ issue["iid"] for issue in mr["related_issues"] ] commit_info["closes_issues"] = [ issue["iid"] for issue in mr["closes_issues"] ] info[sha] = commit_info print(json.dumps(info, indent=2)) if __name__ == "__main__": main()