diff --git a/action.yml b/action.yml index beaff06..a6a42c8 100644 --- a/action.yml +++ b/action.yml @@ -34,8 +34,8 @@ runs: - ${{ inputs.token }} - --label-definitions-file - ${{ inputs.config_file_path }} - - ${{ inputs.command }} - ${{ inputs.target }} + - ${{ inputs.command }} branding: icon: 'bookmark' diff --git a/autolabeler/labelers.py b/autolabeler/labelers.py index 5131e7f..d301c7c 100644 --- a/autolabeler/labelers.py +++ b/autolabeler/labelers.py @@ -164,7 +164,8 @@ def _get_labels_for_selector_matches( except Exception as ex: msg = ( f"Failed to format match data into '{self._name}' and " - f"'{self._description}'. Selector match value were: {match}") + f"'{self._description}'. Selector match value were: " + f"{match}: {ex}") LOG.error(msg) continue @@ -183,9 +184,10 @@ def get_labels_for_repo(self, repo: Repository) -> list[LabelParams]: # If this a simple label with a static name, it always applies to the repo. try: self._name.format() + self._description.format() return [LabelParams(self._name, self._color, self._description)] except KeyError: - # Else, we must run an generate the selector: + # Else, we must run and generate the selectors: return self._get_labels_for_selector_matches( self._run_selectors(repo)) diff --git a/autolabeler/selectors.py b/autolabeler/selectors.py index cb7418d..7d54692 100644 --- a/autolabeler/selectors.py +++ b/autolabeler/selectors.py @@ -181,7 +181,7 @@ def match(self, obj: Repository|PullRequest, # NOTE(aznashwan): Only Repositories/PRs have associated files. if not isinstance(obj, (Repository, PullRequest)): LOG.warn( - f"FileSelector got unsupported object type {type(obj)}: {obj}") + f"{self.__class__}.match() got unsupported object type {type(obj)}: {obj}") return [] all_files = file_cache @@ -199,6 +199,74 @@ def match(self, obj: Repository|PullRequest, return res +class LinesChangedSelector(Selector): + + def __init__( + self, min: int|None=None, max: int|None=None, + change_type: str="total"): + if min is None and max is None: + raise ValueError( + f"{self.__class__}: at least one of min/max is required.") + self._min = min + self._max = max + supported_change_types = [ + "additions", "deletions", "total", "net"] + if change_type not in supported_change_types: + raise ValueError( + f"Unsupported change type {change_type}. " + f"Must be one of: {supported_change_types}") + self._change_type = change_type + + @classmethod + def from_dict(cls, val: dict): + supported_keys = ["min", "max", "type"] + unsupported_keys = [k for k in val if k not in supported_keys] + if unsupported_keys: + raise ValueError( + f"{cls}.from_dict() got unsupported keys: {unsupported_keys}") + return cls( + min=val.get("min"), max=val.get("max"), + change_type=val.get("type", "total")) + + def match(self, obj: Repository|PullRequest, + file_cache: list[str]=[]) -> list[dict]: + _ = file_cache + + # NOTE(aznashwan): Only Repositories/PRs have associated files. + if not isinstance(obj, (PullRequest, Repository)): + LOG.warn( + f"{self.__class__}.match() got unsupported object type {type(obj)}: {obj}") + return [] + + res = { + "lines-changed-min": + self._min if self._min is not None else "-Inf", + "lines-changed-max": + self._max if self._max is not None else "+Inf"} + if isinstance(obj, Repository): + return [res] + + changes = 0 + match self._change_type: + case "total": + changes = obj.additions + obj.deletions + case "additions": + changes = obj.additions + case "deletions": + changes = obj.deletions + case "net": + changes = obj.additions - obj.deletions + case other: + raise ValueError( + f"Got unsupported change totalling scheme: {other}") + + if self._min is not None and changes < self._min: + return [] + if self._max is not None and changes >= self._max: + return [] + return [res] + + class FileLister(): def __init__(self, obj: Repository|PullRequest): @@ -231,6 +299,7 @@ def list_file_paths(self) -> list[str]: SELECTORS_NAME_MAP = { "regex": RegexSelector, "files": FilesSelector, + "lines-changed": LinesChangedSelector, } diff --git a/autolabels.yml b/autolabels.yml index 49cdb39..d2ec83a 100644 --- a/autolabels.yml +++ b/autolabels.yml @@ -70,7 +70,8 @@ pr-size-measure: This PR is between {lines-changed-min} and {lines-changed-max} lines of code. selectors: lines-changed: - min: 0 + type: net # Can be: additions/deletions/total/net (net = additions - deletions) + min: 1 max: 1000 large: @@ -80,5 +81,21 @@ pr-size-measure: selectors: lines-changed: min: 1000 - # NOTE: setting max to zero will remove any upper bound. - max: 0 + max: 10000 + + too-big: + label-color: d73a4a + label-description: | + This PR has more than {lines-changed-min} lines and must be broken down into smaller PRs. + selectors: + lines-changed: + # NOTE: omitting 'max/min' will set the bounds to +/- Infinity respectively. + min: 10000 + action: + close: + # Will only trigger for PRs larger than 10k lines. + on: "{lines-changed-min}" + with-comment: | + Thank you for your contribution, but this PR is too large to be reviewed effectively. + Please break down the changes into individual PRs no larger than {lines-changed-min} lines. + diff --git a/samples/101-showcase.yml b/samples/101-showcase.yml index eddb11d..c96a671 100644 --- a/samples/101-showcase.yml +++ b/samples/101-showcase.yml @@ -1,11 +1,10 @@ -# This defines a static label named 'example-label' with the given props. +# This defines a static static label named 'example-label' with the given props. example-label: - # NOTE: 'label-color' and 'label-description' are always required. label-color: 0075ca # blue label-description: This is a simple example static label. -# This defines two "namespaced" static labels named: label-prefix/sublabel-{1,2}. +# This defines two "namespaced" labels named: label-prefix/sublabel-{1,2}. label-prefix: sublabel-1: label-color: 0075ca @@ -15,7 +14,7 @@ label-prefix: label-description: This is a simple second sublabel. -# This will define as many labels as there are file matches in the repo. +# This will define as many labels as there are filepath regex matches. label-for-sample-file-{files-name-regex-group-1}: label-color: 0075ca # NOTE: "group-0" is the entire filepath. @@ -53,12 +52,9 @@ bug: maintainer-comments-only: false -# This will allow project maintainers to automatically generate and -# set labels on Issues/PRs by commenting '/label-me-as $LABEL': manual-{regex-comments-group-2}: label-color: d73a4a - label-description: | - "This label was defined through a maintainer comment with: {regex-comments-group-1}" + label-description: "This label was defined through a maintainer comment with: {regex-comments-group-1}" selectors: regex: maintainer-comments-only: true @@ -74,7 +70,8 @@ pr-size-measure: This PR is between {lines-changed-min} and {lines-changed-max} lines of code. selectors: lines-changed: - min: 0 + type: net # Can be: additions/deletions/total/net (net = additions - deletions) + min: 1 max: 1000 large: @@ -85,4 +82,19 @@ pr-size-measure: lines-changed: min: 1000 # NOTE: setting max to zero will remove any upper bound. - max: 0 + max: 10000 + + too-big: + label-color: d73a4a + label-description: | + This PR has more than {lines-changed-min} lines and must be broken down into smaller PRs. + action: + close: + # Will only trigger for PRs larger than 10k lines. + on: "{lines-changed-min}" + with-comment: | + Thank you for your contribution, but this PR is too large to be reviewed efficiently. + Please break down the changes into individual PRs no larger than {lines-changed-min} lines. + selectors: + lines-changed: + min: 10000