diff --git a/.github/workflows/tag-maintainers.yml b/.github/workflows/tag-maintainers.yml index d0da3a53..e6dc3494 100644 --- a/.github/workflows/tag-maintainers.yml +++ b/.github/workflows/tag-maintainers.yml @@ -67,52 +67,9 @@ jobs: PR_AUTHOR: "${{ github.event.pull_request.user.login }}" CHANGED_FILES: '${{ steps.changed-files.outputs.changed_files }}' run: | - if [[ -z "$CHANGED_FILES" ]]; then - echo "No plugin files changed. No maintainers to tag." - echo "maintainers=" >> "$GITHUB_OUTPUT" - exit 0 - fi - - echo "Evaluating nixvim meta system for all maintainers..." - # Get a JSON object mapping plugin paths to maintainer data. - ALL_MAINTAINERS=$(nix eval --impure --expr " - let - nixvim = import ./.; - lib = nixvim.inputs.nixpkgs.lib.extend nixvim.lib.overlay; - emptyConfig = lib.nixvim.evalNixvim { - modules = [ { _module.check = false; } ]; - extraSpecialArgs = { pkgs = null; }; - }; - inherit (emptyConfig.config.meta) maintainers; - in - maintainers - " --json 2>/dev/null || echo "{}") - - echo "Finding maintainers for changed files..." - FOUND_MAINTAINERS=$( - echo "$CHANGED_FILES" | while IFS= read -r FILE; do - [[ -z "$FILE" ]] && continue - PLUGIN_DIR=$(dirname "$FILE") - - # Use jq to find maintainers whose path key ends with the plugin directory. - echo "$ALL_MAINTAINERS" | jq -r --arg plugindir "$PLUGIN_DIR" ' - to_entries[] | - select(.key | endswith($plugindir)) | - .value[] | - select(has("github")) | - .github - ' 2>/dev/null - done - ) - - # De-duplicate the list, remove the PR author, and format as a space-separated string. - MAINTAINERS_LIST=$(echo "$FOUND_MAINTAINERS" | grep -v -w -F "$PR_AUTHOR" | sort -u | tr '\n' ' ' | sed 's/ *$//') - - if [[ -z "$MAINTAINERS_LIST" ]]; then - echo "No maintainers found for changed files (or only the PR author is a maintainer)." - else - echo "Found maintainers to notify: $MAINTAINERS_LIST" - fi + MAINTAINERS_LIST=$(./ci/tag-maintainers/extract-maintainers.py \ + --changed-files "$CHANGED_FILES" \ + --pr-author "$PR_AUTHOR") echo "maintainers=$MAINTAINERS_LIST" >> "$GITHUB_OUTPUT" diff --git a/ci/tag-maintainers/extract-maintainers.py b/ci/tag-maintainers/extract-maintainers.py new file mode 100755 index 00000000..d0fdfdc2 --- /dev/null +++ b/ci/tag-maintainers/extract-maintainers.py @@ -0,0 +1,116 @@ +#!/usr/bin/env python3 +""" +Extract maintainers from changed plugin files. + +This script extracts the maintainer extraction logic from the tag-maintainers workflow +for easier testing and validation. +""" + +import argparse +import json +import subprocess +import sys +from typing import List + + +def run_nix_eval(expr: str) -> str: + """Run a Nix evaluation expression and return the result.""" + try: + result = subprocess.run( + ["nix", "eval", "--impure", "--json", "--expr", expr], + capture_output=True, + text=True, + check=True, + ) + return result.stdout.strip() + except subprocess.CalledProcessError as e: + print(f"Error running Nix evaluation: {e}", file=sys.stderr) + print(f"Stderr: {e.stderr}", file=sys.stderr) + return "[]" + + +def extract_maintainers(changed_files: List[str], pr_author: str) -> List[str]: + """Extract maintainers from changed plugin files.""" + if not changed_files: + print("No plugin files changed. No maintainers to tag.", file=sys.stderr) + return [] + + print("Finding maintainers for changed files...", file=sys.stderr) + + changed_files_nix = '[ ' + ' '.join(f'"{f}"' for f in changed_files) + ' ]' + + nix_expr = f""" + let + nixvim = import ./.; + lib = nixvim.inputs.nixpkgs.lib; + emptyConfig = nixvim.lib.nixvim.evalNixvim {{ + modules = [ {{ _module.check = false; }} ]; + extraSpecialArgs.pkgs = null; + }}; + inherit (emptyConfig.config.meta) maintainers; + + changedFiles = {changed_files_nix}; + + # Find maintainers for files that match changed plugin directories + relevantMaintainers = lib.concatLists ( + lib.mapAttrsToList (path: maintainerList: + let + matchingFiles = lib.filter (file: + lib.hasSuffix (dirOf file) path + ) changedFiles; + in + if matchingFiles != [] then maintainerList else [] + ) maintainers + ); + + # Extract GitHub usernames + githubUsers = lib.concatMap (maintainer: + if maintainer ? github then [ maintainer.github ] else [] + ) relevantMaintainers; + + in + lib.unique githubUsers + """ + + result = run_nix_eval(nix_expr) + + try: + maintainers = json.loads(result) + except json.JSONDecodeError: + print(f"Error parsing Nix evaluation result: {result}", file=sys.stderr) + return [] + + filtered_maintainers = [m for m in maintainers if m != pr_author] + + if not filtered_maintainers: + print("No maintainers found for changed files (or only the PR author is a maintainer).", file=sys.stderr) + return [] + else: + print(f"Found maintainers to notify: {' '.join(filtered_maintainers)}", file=sys.stderr) + return filtered_maintainers + + +def main() -> None: + """Main function to handle command line arguments and run the extraction.""" + parser = argparse.ArgumentParser( + description="Extract maintainers from changed plugin files" + ) + parser.add_argument( + "--changed-files", + help="Newline-separated list of changed files", + default="", + ) + parser.add_argument( + "--pr-author", + required=True, + help="GitHub username of the PR author", + ) + + args = parser.parse_args() + changed_files = [f.strip() for f in args.changed_files.split('\n') if f.strip()] + maintainers = extract_maintainers(changed_files, args.pr_author) + print(' '.join(maintainers)) + + +if __name__ == "__main__": + main()