-
Notifications
You must be signed in to change notification settings - Fork 293
[GitHub Actions] python-based workflows based on files changed in a pull request #8
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
31 commits
Select commit
Hold shift + click to select a range
8008af8
Python-based workflows based on files changed in a pull request
jayhawk-commits 93be618
Re-adding sparse checkout block in auto-label workflow
jayhawk-commits 4e616fd
Add step to install requests python module on runners
jayhawk-commits f72c834
Make the json loading the same with both scripts
jayhawk-commits b1390f3
Label application/removal is done by the yaml, so remove this else case
jayhawk-commits 76fca4f
Merge branch 'develop' into joseph-workflowImprovements
jayhawk-commits a590582
Refactored based on review feedback
jayhawk-commits 4091c83
Renaming python scripts to use underscores instead of hyphens
jayhawk-commits 33ed542
Update pr-fanout.yml
jayhawk-commits 5b26c63
Additional code cleanup
jayhawk-commits a139748
Fix python script path
jayhawk-commits 216de30
Interface change when moving from rest api to gh cli
jayhawk-commits a553a33
Update pr_auto_label.py
jayhawk-commits 9c8ceef
Remove token parameter
jayhawk-commits 3f584d2
More refactoring to use the client class
jayhawk-commits 118e9e8
Create a model defining the repos-config.json
jayhawk-commits 7d9b611
Further refactoring and implementation
jayhawk-commits 787eaea
Fix bugs from first checks
jayhawk-commits e429faf
Missing backslashes in multi-line strings
jayhawk-commits 8c12e22
Missed a file in the last commit
jayhawk-commits 4eac095
Reflect checks logic was flawed. Revamped it.
jayhawk-commits 3898021
Update pr_reflect_checks.py
jayhawk-commits 09361ad
Further fixes and improvements based on test runs
jayhawk-commits 0e0b6fd
Some changes weren't saved in the last big commit.
jayhawk-commits 7ce6833
Pushing latest changes before going to bed
jayhawk-commits d21f1c6
Removing reflect checks workflow from this pull request
jayhawk-commits 4c7f8b3
Delete github_api_client.py
jayhawk-commits c102b68
Introducing fanout naming utils class
jayhawk-commits f2c3821
Applying feedback from PR comments on minor things.
jayhawk-commits ec48efe
Missed this file during staging of last commit.
jayhawk-commits df7c2a6
Removing obsolete debug code.
jayhawk-commits File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| version: 2 | ||
| updates: | ||
|
|
||
| # Check for updates to GitHub Actions | ||
| - package-ecosystem: "github-actions" | ||
| directory: "/" | ||
| schedule: | ||
| interval: "weekly" | ||
| groups: | ||
| github-actions: | ||
| patterns: | ||
| - "*" |
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
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| import json | ||
| import sys | ||
| import logging | ||
| from typing import List | ||
| from repo_config_model import RepoConfig, RepoEntry | ||
|
|
||
| logger = logging.getLogger(__name__) | ||
|
|
||
| def load_repo_config(config_path: str) -> List[RepoEntry]: | ||
| """Load and validate repository config from JSON using Pydantic.""" | ||
| try: | ||
| with open(config_path, "r", encoding="utf-8") as f: | ||
| data = json.load(f) | ||
| config = RepoConfig(**data) | ||
| return config.repositories | ||
| except Exception as e: | ||
| logger.error(f"Failed to load or validate config file '{config_path}': {e}") | ||
| sys.exit(1) |
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,142 @@ | ||
| #!/usr/bin/env python3 | ||
|
|
||
| """ | ||
| GitHub CLI Client Utility | ||
| ------------------------- | ||
| This utility provides a GitHubCLIClient class that wraps GitHub CLI (gh) operations | ||
| used across automation scripts, such as retrieving pull request file changes and labels. | ||
|
|
||
| When doing manual testing, you can run the same gh commands directly in the terminal. | ||
| These commands will be output by the debug logging in debug mode. | ||
|
|
||
| Requirements: | ||
| - GitHub CLI (`gh`) must be installed and authenticated. | ||
| - NOTE: GH_TOKEN environment variable hands authentication token to the CLI in a runner. | ||
| - The repository must be accessible to the authenticated user. | ||
| """ | ||
|
|
||
| import subprocess | ||
| import json | ||
| import logging | ||
| from typing import List, Optional | ||
|
|
||
| logger = logging.getLogger(__name__) | ||
|
|
||
| class GitHubCLIClient: | ||
| def __init__(self) -> None: | ||
| """Initialize the GitHub CLI client.""" | ||
| if not self._gh_available(): | ||
| raise EnvironmentError("GitHub CLI (`gh`) is not installed or not in PATH.") | ||
|
|
||
| def _gh_available(self) -> bool: | ||
| """Check if GitHub CLI is available.""" | ||
| try: | ||
| subprocess.run(["gh", "--version"], check=True, stdout=subprocess.DEVNULL) | ||
| return True | ||
| except subprocess.CalledProcessError: | ||
| return False | ||
|
|
||
| def _run_gh_command(self, args: List[str], dry_run: Optional[bool] = False) -> subprocess.CompletedProcess: | ||
| """Run a `gh` CLI command and return the result.""" | ||
| cmd = ["gh"] + args | ||
| logger.debug(f"Running command: {' '.join(cmd)}") | ||
| # dry_run option only matters for operations that write to GitHub | ||
| if dry_run: | ||
| result = subprocess.CompletedProcess(cmd, 0, stdout="Dry run enabled. No changes made.", stderr="") | ||
| else: | ||
| result = subprocess.run(cmd, capture_output=True, text=True) | ||
| if result.returncode != 0: | ||
| logger.error(f"Command failed: {' '.join(cmd)}\n{result.stderr.strip()}") | ||
| raise subprocess.CalledProcessError(result.returncode, cmd, result.stdout, result.stderr) | ||
| return result | ||
|
|
||
| def get_changed_files(self, repo: str, pr: int) -> List[str]: | ||
| """Fetch the changed files in a pull request using `gh` CLI.""" | ||
| result = self._run_gh_command( | ||
| ["pr", "view", str(pr), "--repo", repo, "--json", "files"], | ||
| ) | ||
| data = json.loads(result.stdout) | ||
| files = [f["path"] for f in data.get("files", [])] | ||
| logger.debug(f"Changed files in PR #{pr}: {files}") | ||
| return files | ||
|
|
||
| def get_defined_labels(self, repo: str) -> List[str]: | ||
| """Get all labels defined in the given repository.""" | ||
| result = self._run_gh_command(["label", "list", "--repo", repo, "--json", "name"]) | ||
| return [label["name"] for label in json.loads(result.stdout)] | ||
|
|
||
| def get_existing_labels_on_pr(self, repo: str, pr: int) -> List[str]: | ||
| """Fetch current labels on a PR.""" | ||
| result = self._run_gh_command( | ||
| ["pr", "view", str(pr), "--repo", repo, "--json", "labels"] | ||
| ) | ||
| data = json.loads(result.stdout) | ||
| labels = [label["name"] for label in data.get("labels", [])] | ||
| logger.debug(f"Existing labels on PR #{pr}: {labels}") | ||
| return labels | ||
|
|
||
| def pr_view(self, repo: str, head: str) -> Optional[int]: | ||
| """Check if a PR exists for the given repo and branch.""" | ||
| try: | ||
| result = self._run_gh_command(["pr", "list", "--json", "number", "--repo", repo, "--head", head]) | ||
| pr_list = json.loads(result.stdout) | ||
| return pr_list[0]["number"] if pr_list else None | ||
| except subprocess.CalledProcessError: | ||
| logger.warning(f"Failed to retrieve PR from {repo} with head {head}") | ||
| return None # PR does not exist | ||
|
|
||
| def get_pr_by_head_branch(self, repo: str, head: str) -> Optional[dict]: | ||
| """Get the PR object for a given head branch in a repository, if it exists.""" | ||
| try: | ||
| result = self._run_gh_command(["pr", "list", "--json", "number,title,state", "--repo", repo, "--head", head]) | ||
| pr_list = json.loads(result.stdout) | ||
| return pr_list[0] if pr_list else None | ||
| except subprocess.CalledProcessError: | ||
| logger.warning(f"Failed to retrieve PR from {repo} with head {head}") | ||
| return None | ||
|
|
||
| def pr_create(self, repo: str, base: str, head: str, title: str, body: str, dry_run: Optional[bool] = False) -> None: | ||
| """Create a new pull request.""" | ||
| cmd = [ | ||
| "pr", "create", | ||
| "--repo", repo, | ||
| "--base", base, | ||
| "--head", head, | ||
| "--title", title, | ||
| "--body", body | ||
| ] | ||
| self._run_gh_command(cmd, dry_run=dry_run) | ||
| logger.info(f"Created PR from {head} to {base} in {repo}.") | ||
|
|
||
| def close_pr_and_delete_branch(self, repo: str, pr_number: int, dry_run: Optional[bool] = False) -> None: | ||
| """Close a pull request and delete the associated branch using the GitHub CLI.""" | ||
| cmd = ["pr", "close", str(pr_number), "--repo", repo, "--delete-branch"] | ||
| if dry_run: | ||
| logger.info(f"Dry run: The pull request #{pr_number} would be closed and the branch would be deleted in repo '{repo}'") | ||
| else: | ||
| self._run_gh_command(cmd) | ||
| logger.info(f"Closed pull request #{pr_number} and deleted the associated branch in repo '{repo}'") | ||
|
|
||
| def sync_labels(self, target_repo: str, pr_number: int, labels: List[str], dry_run: Optional[bool] = False) -> None: | ||
| """Sync labels from the source repo to the target repo (only apply existing labels).""" | ||
| logger.debug(f"Syncing labels to {target_repo} PR #{pr_number}.") | ||
| result = self._run_gh_command( | ||
| ["label", "list", "--repo", target_repo, "--json", "name"] | ||
| ) | ||
| target_repo_labels = {label["name"] for label in json.loads(result.stdout)} | ||
| labels_set = set(labels) | ||
| labels_to_apply = labels_set & target_repo_labels | ||
| # Apply labels that exist in both source PR and target repos | ||
| # Wrap in quotes if label contains spaces | ||
| labels_arg = ",".join(f'"{label}"' if " " in label else label for label in labels_to_apply) | ||
| cmd = [ | ||
| "pr", "edit", | ||
| str(pr_number), | ||
| "--repo", target_repo, | ||
| "--add-label", labels_arg | ||
| ] | ||
| if not dry_run: | ||
| self._run_gh_command(cmd, dry_run=dry_run) | ||
| logger.info(f"Applied labels '{labels_arg}' to PR #{pr_number} in {target_repo}.") | ||
| else: | ||
| logger.info(f"Dry run: Labels '{labels_arg}' would be applied to PR #{pr_number} in {target_repo}.") | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.