#!/usr/bin/env python3 import argparse import os import re import subprocess import json from packaging.version import parse as parse_version from functools import lru_cache @lru_cache(maxsize=32) def get_tag_version_with_gh(repo, sha, debug): cmd = ['gh', 'api', f'repos/{repo}/git/refs/tags', '--paginate'] try: output = subprocess.check_output(cmd).decode('utf-8') except subprocess.CalledProcessError as e: print(f"An error occurred: {str(e)}. Standard Error Output: {e.stderr}") return None tags_data = json.loads(output) possible_tags = [] for entry in tags_data: if 'object' in entry and entry['object']['sha'] == sha: possible_tags.append(entry['ref'].split('/')[-1]) if debug: print(f"Debug: Available tags for {repo}@{sha}: {possible_tags}") # Separate semantic and non-semantic versions semver_tags = [] non_semver_tags = [] for tag in possible_tags: try: version = parse_version(tag) if type(version) is not str: semver_tags.append((version, tag)) else: non_semver_tags.append(tag) except: non_semver_tags.append(tag) # Sort semantic versions semver_tags.sort(key=lambda x: (x[0].epoch, x[0].release, x[0].pre, x[0].dev, x[0].post), reverse=True) # Sort non-semantic versions non_semver_tags.sort(reverse=True) # Prefer semantic versions if available if semver_tags: return semver_tags[0][1] elif non_semver_tags: return non_semver_tags[0] else: return None def update_yaml_comments(yaml_lines, debug): updated_lines = [] # Using the updated regex pattern with named groups pattern = re.compile(r"uses: (?P<repo>[^/@]+/[^/@]+)(?:/[^@]*)?@(?P<sha>[a-f0-9]{40})") for line in yaml_lines: match = pattern.search(line) if match: # Extract named groups "repo" and "sha" repo = match.group("repo") sha = match.group("sha") tag_version = get_tag_version_with_gh(repo, sha, debug) # Remove any existing comment starting with "# tag=" line_content = re.sub(r"( # tag=[^\s]+)?", "", line) if tag_version: if debug: print(f"Debug: Selected tag for {repo}@{sha}: {tag_version}") # Add the new comment, preserving any original newlines line = f"{line_content.rstrip()} # tag={tag_version}{os.linesep}" updated_lines.append(line) return updated_lines def main(): parser = argparse.ArgumentParser(description='Update GitHub Actions YAML file with tag comments.') parser.add_argument('files', type=str, nargs='+', help='Paths to the GitHub Actions YAML files.') parser.add_argument('-i', '--in-place', action='store_true', help='Modify the files in-place.') parser.add_argument('--debug', action='store_true', help='Enable debug output.') args = parser.parse_args() for file_path in args.files: with open(file_path, 'r') as f: yaml_lines = f.readlines() updated_yaml_lines = update_yaml_comments(yaml_lines, args.debug) if args.in_place: with open(file_path, 'w') as f: f.writelines(updated_yaml_lines) else: print(f"--- {file_path} ---") print("".join(updated_yaml_lines)) if __name__ == "__main__": main()