-
Notifications
You must be signed in to change notification settings - Fork 910
[PM-14880] ci: Add automated PR labelling based on file paths and title patterns #6157
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
Changes from all commits
c79e985
1e58893
d204171
125a2f6
d3a3285
025a994
8cfa32d
dbccb55
18a4838
104d510
b6aae4d
e2279b3
bd15be2
9e3c584
470158c
51c539e
3578b08
352a2e4
bcf6981
d339d5d
a3b7def
043021f
99983ca
c6505e3
625d9a7
7b32be4
ee72ba2
701c404
1e39e26
51677ba
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,49 @@ | ||
| { | ||
| "catch_all_label": "t:misc", | ||
| "title_patterns": { | ||
| "t:new-feature": ["feat", "feature"], | ||
| "t:enhancement": ["enhancement", "enh", "impr"], | ||
| "t:bug": ["fix", "bug", "bugfix"], | ||
| "t:tech-debt": ["refactor", "chore", "cleanup", "revert", "debt", "test", "perf"], | ||
| "t:docs": ["docs"], | ||
| "t:ci": ["ci", "build", "chore(ci)"], | ||
| "t:deps": ["deps"], | ||
| "t:breaking-change": ["breaking", "breaking-change"], | ||
| "t:misc": ["misc"] | ||
| }, | ||
| "path_patterns": { | ||
| "app:shared": [ | ||
| "annotation/", | ||
| "core/", | ||
| "data/", | ||
| "network/", | ||
| "ui/", | ||
| "authenticatorbridge/", | ||
| "gradle/" | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ๐ญ Pattern overlap: DetailsThe
When a PR modifies This might be intentional (gradle changes affect both apps and are dependency-related), but worth confirming this is the desired behavior. If gradle changes should only be labeled as |
||
| ], | ||
| "app:password-manager": [ | ||
| "app/", | ||
| "cxf/" | ||
| ], | ||
| "app:authenticator": [ | ||
| "authenticator/" | ||
| ], | ||
| "t:ci": [ | ||
| ".github/", | ||
| "scripts/", | ||
| "fastlane/", | ||
| ".gradle/", | ||
| ".claude/", | ||
| "detekt-config.yml" | ||
| ], | ||
| "t:docs": [ | ||
| "docs/" | ||
| ], | ||
| "t:deps": [ | ||
| "gradle/" | ||
vvolkgang marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| ], | ||
| "t:misc": [ | ||
| "keystore/" | ||
| ] | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,238 @@ | ||
| #!/usr/bin/env python3 | ||
| # Requires Python 3.9+ | ||
| """ | ||
| Label pull requests based on changed file paths and PR title patterns (conventional commit format). | ||
|
|
||
| Usage: | ||
| python label-pr.py <pr-number> [-a|--add|-r|--replace] [-d|--dry-run] [-c|--config CONFIG] | ||
|
|
||
| Arguments: | ||
| pr-number: The pull request number | ||
| -a, --add: Add labels without removing existing ones (default) | ||
vvolkgang marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| -r, --replace: Replace all existing labels | ||
| -d, --dry-run: Run without actually applying labels | ||
| -c, --config: Path to JSON config file (default: .github/label-pr.json) | ||
|
|
||
| Examples: | ||
| python label-pr.py 1234 | ||
| python label-pr.py 1234 -a | ||
| python label-pr.py 1234 --replace | ||
| python label-pr.py 1234 -r -d | ||
| python label-pr.py 1234 --config custom-config.json | ||
| """ | ||
|
|
||
| import argparse | ||
| import json | ||
| import os | ||
| import subprocess | ||
| import sys | ||
|
|
||
| DEFAULT_MODE = "add" | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. DetailsThe constant -a, --add: Add labels without removing existing ones (default)
-r, --replace: Replace all existing labelsHowever, the implementation at line 202 defaults to mode = "replace" if args.replace else "add"This means when neither flag is provided, the mode is Recommendation: Clarify whether the default should be |
||
| DEFAULT_CONFIG_PATH = ".github/label-pr.json" | ||
|
|
||
| def load_config_json(config_file: str) -> dict: | ||
| """Load configuration from JSON file.""" | ||
| if not os.path.exists(config_file): | ||
| print(f"โ Config file not found: {config_file}") | ||
| sys.exit(1) | ||
|
|
||
| try: | ||
| with open(config_file, 'r') as f: | ||
| config = json.load(f) | ||
| print(f"โ Loaded config from: {config_file}") | ||
|
|
||
| valid_config = True | ||
| if not config.get("catch_all_label"): | ||
| print("โ Missing 'catch_all_label' in config file") | ||
| valid_config = False | ||
| if not config.get("title_patterns"): | ||
| print("โ Missing 'title_patterns' in config file") | ||
| valid_config = False | ||
| if not config.get("path_patterns"): | ||
| print("โ Missing 'path_patterns' in config file") | ||
| valid_config = False | ||
|
|
||
| if not valid_config: | ||
| print("::error::Invalid label-pr.json config file, exiting...") | ||
| sys.exit(1) | ||
|
|
||
| return config | ||
| except json.JSONDecodeError as e: | ||
| print(f"โ JSON deserialization error in label-pr.json config: {e}") | ||
| sys.exit(1) | ||
| except Exception as e: | ||
| print(f"โ Unexpected error loading label-pr.json config: {e}") | ||
| sys.exit(1) | ||
|
|
||
| def gh_get_changed_files(pr_number: str) -> list[str]: | ||
| """Get list of changed files in a pull request.""" | ||
| try: | ||
| result = subprocess.run( | ||
| ["gh", "pr", "diff", pr_number, "--name-only"], | ||
| capture_output=True, | ||
| text=True, | ||
| check=True | ||
| ) | ||
| changed_files = result.stdout.strip().split("\n") | ||
| return list(filter(None, changed_files)) | ||
| except subprocess.CalledProcessError as e: | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. DetailsWhen The caller at line 212 doesn't distinguish between:
Current behavior: changed_files = gh_get_changed_files(pr_number) # Returns [] on error
print("๐ Changed files:\n" + "\n".join(changed_files) + "\n") # Prints empty listRecommendation:
def gh_get_changed_files(pr_number: str) -> list[str]:
try:
# ... existing code ...
except subprocess.CalledProcessError as e:
print(f"::error::Error getting changed files: {e}")
sys.exit(1) # Critical failure
changed_files = gh_get_changed_files(pr_number)
if changed_files is None: # Return None on error instead of []
print("::error::Failed to retrieve changed files")
sys.exit(1)Same applies to
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. DetailsWhen The caller at line 212 cannot distinguish between:
Current behavior: changed_files = gh_get_changed_files(pr_number) # Returns [] on error
print("๐ Changed files:\n" + "\n".join(changed_files) + "\n") # Prints empty listRecommendation: Either exit on critical failures or return def gh_get_changed_files(pr_number: str) -> list[str] | None:
try:
# ... existing code ...
except subprocess.CalledProcessError as e:
print(f"::error::Error getting changed files: {e}")
return None # Signal error explicitly
# At call site:
changed_files = gh_get_changed_files(pr_number)
if changed_files is None:
print("::error::Failed to retrieve changed files")
sys.exit(1)Same issue exists in |
||
| print(f"::error::Error getting changed files: {e}") | ||
| return [] | ||
|
|
||
| def gh_get_pr_title(pr_number: str) -> str: | ||
| """Get the title of a pull request.""" | ||
| try: | ||
| result = subprocess.run( | ||
| ["gh", "pr", "view", pr_number, "--json", "title", "--jq", ".title"], | ||
| capture_output=True, | ||
| text=True, | ||
| check=True | ||
| ) | ||
| return result.stdout.strip() | ||
| except subprocess.CalledProcessError as e: | ||
| print(f"::error::Error getting PR title: {e}") | ||
| return "" | ||
|
|
||
| def gh_add_labels(pr_number: str, labels: list[str]) -> None: | ||
| """Add labels to a pull request (doesn't remove existing labels).""" | ||
| gh_labels = ','.join(labels) | ||
| subprocess.run( | ||
| ["gh", "pr", "edit", pr_number, "--add-label", gh_labels], | ||
| check=True | ||
| ) | ||
|
|
||
| def gh_replace_labels(pr_number: str, labels: list[str]) -> None: | ||
| """Replace all labels on a pull request with the specified labels.""" | ||
| payload = json.dumps({"labels": labels}) | ||
| subprocess.run( | ||
| ["gh", "api", "repos/{owner}/{repo}/issues/" + pr_number, "-X", "PATCH", "--silent", "--input", "-"], | ||
| input=payload, | ||
| text=True, | ||
| check=True | ||
| ) | ||
|
|
||
| def label_filepaths(changed_files: list[str], path_patterns: dict) -> list[str]: | ||
| """Check changed files against path patterns and return labels to apply.""" | ||
| if not changed_files: | ||
| return [] | ||
|
|
||
| labels_to_apply = set() # Use set to avoid duplicates | ||
|
|
||
| for label, patterns in path_patterns.items(): | ||
| for file in changed_files: | ||
| if any(file.startswith(pattern) for pattern in patterns): | ||
| print(f"๐ File '{file}' matches pattern for label '{label}'") | ||
| labels_to_apply.add(label) | ||
| break | ||
|
|
||
| if "app:shared" in labels_to_apply: | ||
| labels_to_apply.add("app:password-manager") | ||
| labels_to_apply.add("app:authenticator") | ||
| labels_to_apply.remove("app:shared") | ||
|
|
||
| if not labels_to_apply: | ||
| print("::warning::No matching file paths found, no labels applied.") | ||
|
|
||
| return list(labels_to_apply) | ||
|
|
||
| def label_title(pr_title: str, title_patterns: dict) -> list[str]: | ||
| """Check PR title against patterns and return labels to apply.""" | ||
| if not pr_title: | ||
| return [] | ||
|
|
||
| labels_to_apply = set() | ||
| title_lower = pr_title.lower() | ||
| for label, patterns in title_patterns.items(): | ||
| for pattern in patterns: | ||
| # Check for pattern with : or ( suffix (conventional commits format) | ||
| if f"{pattern}:" in title_lower or f"{pattern}(" in title_lower: | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ๐จ Title pattern matching could be more precise DetailsCurrent logic checks if pattern appears anywhere in the title with if f"{pattern}:" in title_lower or f"{pattern}(" in title_lower:This could produce false positives. Examples:
Recommendation: # Check for pattern at beginning or after whitespace
import re
pattern_regex = rf"(^|\s){re.escape(pattern)}[:(]"
if re.search(pattern_regex, title_lower):
print(f"๐ Title matches pattern '{pattern}' for label '{label}'")
labels_to_apply.add(label)
breakOr simpler string-based approach: # Check pattern is at start or preceded by space
if title_lower.startswith(f"{pattern}:") or title_lower.startswith(f"{pattern}(") or \
f" {pattern}:" in title_lower or f" {pattern}(" in title_lower:Current implementation works for well-formatted conventional commits, but could be more robust.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ๐จ Title pattern matching could produce false positives. DetailsCurrent logic checks if pattern appears anywhere in the title: if f"{pattern}:" in title_lower or f"{pattern}(" in title_lower:This could match incorrectly. Examples:
Recommendation: Use more precise matching to ensure pattern is at word boundary: import re
# At function level, check pattern at start or after whitespace
pattern_regex = rf"(^|\s){re.escape(pattern)}[:(]"
if re.search(pattern_regex, title_lower):
print(f"๐ Title matches pattern '{pattern}' for label '{label}'")
labels_to_apply.add(label)
breakOr simpler string-based approach: # Check pattern is at start or preceded by space
if title_lower.startswith(f"{pattern}:") or title_lower.startswith(f"{pattern}(") or \
f" {pattern}:" in title_lower or f" {pattern}(" in title_lower:Current implementation works for well-formatted conventional commits, but could be more robust. |
||
| print(f"๐ Title matches pattern '{pattern}' for label '{label}'") | ||
| labels_to_apply.add(label) | ||
| break | ||
|
|
||
| if not labels_to_apply: | ||
| print("::warning::No matching title patterns found, no labels applied.") | ||
|
|
||
| return list(labels_to_apply) | ||
|
|
||
| def parse_args(): | ||
| """Parse command line arguments.""" | ||
| parser = argparse.ArgumentParser( | ||
| description="Label pull requests based on changed file paths and PR title patterns." | ||
| ) | ||
| parser.add_argument( | ||
| "pr_number", | ||
| help="The pull request number" | ||
| ) | ||
|
|
||
| mode_group = parser.add_mutually_exclusive_group() | ||
| mode_group.add_argument( | ||
| "-a", "--add", | ||
| action="store_true", | ||
| help="Add labels without removing existing ones (default)" | ||
| ) | ||
| mode_group.add_argument( | ||
| "-r", "--replace", | ||
| action="store_true", | ||
| help="Replace all existing labels" | ||
| ) | ||
|
|
||
| parser.add_argument( | ||
| "-d", "--dry-run", | ||
| action="store_true", | ||
| help="Run without actually applying labels" | ||
| ) | ||
|
|
||
| parser.add_argument( | ||
| "-c", "--config", | ||
| default=DEFAULT_CONFIG_PATH, | ||
| help=f"Path to JSON config file (default: {DEFAULT_CONFIG_PATH})" | ||
| ) | ||
| args, unknown = parser.parse_known_args() # required to handle --dry-run passed as an empty string ("") by the workflow | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ๐ญ DetailsThe comment states this is "required to handle --dry-run passed as an empty string". This happens because the workflow at _DRY_RUN: ${{ inputs.dry-run == true && '--dry-run' || '' }}When python3 .github/scripts/label-pr.py "$_PR_NUMBER" "$_LABEL_MODE" ""The empty string is passed as a positional argument, which Recommendation: Fix at the workflow level instead of working around in Python: run: |
if [ -n "$_DRY_RUN" ]; then
python3 .github/scripts/label-pr.py "$_PR_NUMBER" "$_LABEL_MODE" "$_DRY_RUN"
else
python3 .github/scripts/label-pr.py "$_PR_NUMBER" "$_LABEL_MODE"
fiThis eliminates the need for |
||
| return args | ||
|
|
||
| def main(): | ||
| args = parse_args() | ||
| config = load_config_json(args.config) | ||
| CATCH_ALL_LABEL = config["catch_all_label"] | ||
| LABEL_TITLE_PATTERNS = config["title_patterns"] | ||
| LABEL_PATH_PATTERNS = config["path_patterns"] | ||
|
|
||
| pr_number = args.pr_number | ||
| mode = "replace" if args.replace else "add" | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. DetailsThe implementation defaults to mode = "replace" if args.replace else "add"However, the documentation at lines 11-12 states: -a, --add: Add labels without removing existing ones (default)
-r, --replace: Replace all existing labelsThis creates confusion about actual vs documented behavior. The workflow at Recommendation: -a, --add: Add labels without removing existing ones
-r, --replace: Replace all existing labels (default for normal PRs)Or update |
||
|
|
||
| if args.dry_run: | ||
| print("๐ DRY RUN MODE - Labels will not be applied") | ||
| print(f"๐ Labeling mode: {mode}") | ||
| print(f"๐ Checking PR #{pr_number}...") | ||
|
|
||
| pr_title = gh_get_pr_title(pr_number) | ||
| print(f"๐ PR Title: {pr_title}\n") | ||
|
|
||
| changed_files = gh_get_changed_files(pr_number) | ||
| print("๐ Changed files:\n" + "\n".join(changed_files) + "\n") | ||
|
|
||
| filepath_labels = label_filepaths(changed_files, LABEL_PATH_PATTERNS) | ||
| title_labels = label_title(pr_title, LABEL_TITLE_PATTERNS) | ||
| all_labels = set(filepath_labels + title_labels) | ||
|
|
||
| if not any(label.startswith("t:") for label in all_labels): | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ๐ญ Catch-all label logic may produce unexpected results DetailsThe catch-all label ( if not any(label.startswith("t:") for label in all_labels):
all_labels.add(CATCH_ALL_LABEL)Scenario: A PR that:
Result: Only Question: Should the catch-all only apply when both filepath and title labeling fail to produce type labels, or is the current behavior (adding Consider if this edge case needs explicit documentation or if the logic should check: # Only add catch-all if BOTH filepath and title labeling found no type labels
if not title_labels and not any(label.startswith("t:") for label in filepath_labels):
all_labels.add(CATCH_ALL_LABEL)Current behavior seems reasonable, but worth confirming intent. |
||
| all_labels.add(CATCH_ALL_LABEL) | ||
|
|
||
| if all_labels: | ||
| labels_str = ', '.join(sorted(all_labels)) | ||
| if mode == "add": | ||
| print(f"๐ท๏ธ Adding labels: {labels_str}") | ||
| if not args.dry_run: | ||
| gh_add_labels(pr_number, list(all_labels)) | ||
| else: | ||
| print(f"๐ท๏ธ Replacing labels with: {labels_str}") | ||
| if not args.dry_run: | ||
| gh_replace_labels(pr_number, list(all_labels)) | ||
| else: | ||
| print("โน๏ธ No matching patterns found, no labels applied.") | ||
|
|
||
| print("โ Done") | ||
|
|
||
| if __name__ == "__main__": | ||
| main() | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,80 @@ | ||
| name: SDLC / Label PR by Files | ||
|
|
||
| on: | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ๐ญ Workflow only supports manual dispatch - automated triggers not yet configured. DetailsThe workflow currently only triggers via
This is appropriate for initial testing. When adding automated triggers, consider: on:
pull_request:
types: [opened, synchronize, reopened]
pull_request_target: # For fork PRs with write permissions
types: [opened, synchronize, reopened]
workflow_dispatch:
# ... keep existing inputs for manual testingSecurity consideration: When adding Current implementation is safe for testing phase. |
||
| workflow_dispatch: | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ๐ญ Workflow only supports manual dispatch - PR events not yet configured DetailsThe workflow currently only triggers via
This is fine for initial testing, but worth noting that automated labeling won't occur until the trigger is updated to something like: on:
pull_request:
types: [opened, synchronize, reopened]
pull_request_target: # For fork PRs
types: [opened, synchronize, reopened]
workflow_dispatch:
# ... keep existing inputs for manual testingSecurity consideration: When adding Current implementation is safe for testing phase. Just flagging for future work. |
||
| inputs: | ||
| pr-number: | ||
| description: "Pull Request Number" | ||
| required: true | ||
| type: number | ||
| mode: | ||
| description: "Labeling Mode" | ||
| type: choice | ||
| options: | ||
| - add | ||
| - replace | ||
| default: add | ||
| dry-run: | ||
| description: "Dry Run - Don't apply labels" | ||
| type: boolean | ||
| default: false | ||
|
|
||
| jobs: | ||
| label-pr: | ||
| name: Label PR by Changed Files | ||
| runs-on: ubuntu-24.04 | ||
| permissions: | ||
| pull-requests: write # required to update labels | ||
| contents: read | ||
|
|
||
| steps: | ||
| - name: Check out repository | ||
| uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 | ||
| with: | ||
| persist-credentials: false | ||
|
|
||
| - name: Determine label mode for Pull Request | ||
| id: label-mode | ||
| env: | ||
| GH_TOKEN: ${{ github.token }} | ||
| _PR_NUMBER: ${{ inputs.pr-number }} | ||
| _PR_USER: ${{ github.event.pull_request.user.login }} | ||
vvolkgang marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| _IS_FORK: ${{ github.event.pull_request.head.repo.fork }} | ||
| run: | | ||
| # Support workflow_dispatch testing by retrieving PR data | ||
| if [ -z "$_PR_USER" ]; then | ||
| echo "๐ PR User is empty, retrieving PR data for PR #$_PR_NUMBER..." | ||
| PR_DATA=$(gh pr view "$_PR_NUMBER" --json author,isCrossRepository) | ||
| _PR_USER=$(echo "$PR_DATA" | jq -r '.author.login') | ||
| _IS_FORK=$(echo "$PR_DATA" | jq -r '.isCrossRepository') | ||
| fi | ||
| echo "๐ PR User: $_PR_USER" | ||
| echo "๐ Is Fork: $_IS_FORK" | ||
| # Handle PRs with labels set by other automations by adding instead of replacing | ||
| if [ "$_IS_FORK" = "true" ]; then | ||
| echo "โก๏ธ Fork PR ($_PR_USER). Label mode: --add" | ||
| echo "label_mode=--add" >> "$GITHUB_OUTPUT" | ||
| exit 0 | ||
| fi | ||
| if [ "$_PR_USER" = "renovate[bot]" ] || [ "$_PR_USER" = "bw-ghapp[bot]" ]; then | ||
| echo "โก๏ธ Bot PR ($_PR_USER). Label mode: --add" | ||
| echo "label_mode=--add" >> "$GITHUB_OUTPUT" | ||
| exit 0 | ||
| fi | ||
| echo "โก๏ธ Normal PR. Label mode: --replace" | ||
| echo "label_mode=--replace" >> "$GITHUB_OUTPUT" | ||
| - name: Label PR based on changed files | ||
| env: | ||
| GH_TOKEN: ${{ github.token }} | ||
| _PR_NUMBER: ${{ inputs.pr-number || github.event.pull_request.number }} | ||
| _LABEL_MODE: ${{ inputs.mode && format('--{0}', inputs.mode) || steps.label-mode.outputs.label_mode }} | ||
| _DRY_RUN: ${{ inputs.dry-run == true && '--dry-run' || '' }} | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
DetailsWhen _DRY_RUN: ${{ inputs.dry-run == true && '--dry-run' || '' }}This empty string is then passed as a positional argument to the Python script: python3 .github/scripts/label-pr.py "$_PR_NUMBER" "$_LABEL_MODE" "$_DRY_RUN"When python3 .github/scripts/label-pr.py "1234" "--replace" ""The Python script receives an empty string as the third argument, which Recommendation: - name: Label PR based on changed files
env:
GH_TOKEN: ${{ github.token }}
_PR_NUMBER: ${{ inputs.pr-number || github.event.pull_request.number }}
_LABEL_MODE: ${{ inputs.mode && format('--{0}', inputs.mode) || steps.label-mode.outputs.label_mode }}
_DRY_RUN: ${{ inputs.dry-run == true && '--dry-run' || '' }}
run: |
if [ -n "$_DRY_RUN" ]; then
python3 .github/scripts/label-pr.py "$_PR_NUMBER" "$_LABEL_MODE" "$_DRY_RUN"
else
python3 .github/scripts/label-pr.py "$_PR_NUMBER" "$_LABEL_MODE"
fiThis eliminates the need for the |
||
| run: | | ||
| echo "๐ Labeling PR #$_PR_NUMBER with mode: $_LABEL_MODE and dry-run: $_DRY_RUN" | ||
| python3 .github/scripts/label-pr.py "$_PR_NUMBER" "$_LABEL_MODE" "$_DRY_RUN" | ||
This file was deleted.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
๐ญ Pattern overlap:
gradle/appears in bothapp:sharedandt:depsDetails
The
gradle/directory pattern is defined in:app:shared(line 22): Triggersapp:password-manager+app:authenticatorlabelst:deps(line 43): Triggerst:depslabelWhen a PR modifies
gradle/files, it will receive three labels:app:password-manager,app:authenticator, andt:deps.This might be intentional (gradle changes affect both apps and are dependency-related), but worth confirming this is the desired behavior. If gradle changes should only be labeled as
t:deps, remove it from theapp:sharedlist.