Skip to content
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

Automatic enforcement of the 5 day rule #147

Closed
chrisgorgo opened this issue Feb 12, 2019 · 15 comments
Closed

Automatic enforcement of the 5 day rule #147

chrisgorgo opened this issue Feb 12, 2019 · 15 comments
Labels
enhancement New feature or request help wanted Extra attention is needed infrastructure

Comments

@chrisgorgo
Copy link
Contributor

According to https://github.com/bids-standard/bids-specification/blob/master/DECISION-MAKING.md#rules PRs should not be merged sooner than 5 days from the last commit. Right now this rule needs to be manually observed by members with write permissions.

We could automate this by creating a bot that will a) give a request changes review to PRs younger than 5 days and remove it when the 5 day period passes b) merge mergeable PRs.

@chrisgorgo chrisgorgo added enhancement New feature or request help wanted Extra attention is needed labels Feb 12, 2019
@emdupre
Copy link
Collaborator

emdupre commented Feb 12, 2019

Killing two birds with one stone -- I think a bot will clear up any ambiguity about when that 5-day clock starts ! 👍

@emdupre
Copy link
Collaborator

emdupre commented Feb 12, 2019

I wonder if we could write a rule for Policy Bot.

@chrisgorgo
Copy link
Contributor Author

Investigating if we can do this with mergify.io Mergifyio/mergify#349

@KirstieJane KirstieJane mentioned this issue Feb 18, 2019
@emdupre
Copy link
Collaborator

emdupre commented Feb 18, 2019

Based on discussion in #150, it sounds like we should write rules for disapproval that will only be lifted after 5 days from oldest commit. That way the merging reviewer will still be able to ensure that the PR title is correct for the changelog. Unless anyone else has another suggestion for how to approach this issue !

@sappelhoff
Copy link
Member

That way the merging reviewer will still be able to ensure that the PR title is correct for the changelog.

+1, this would be more robust instead of automatically merging after 5 days if 2 approving reviews,

@gallexi
Copy link

gallexi commented Mar 8, 2019

Hello! I'm just passing through while exploring OSS x Neuroscience, but I did a bit of research last night and found Mergeable, a Probot plugin. I'm not sure if this will serve your needs completely, since it doesn't seem compatible with PushEvents (which, I'm pretty sure, is the particular event you need to be watching for/timing) but perhaps it could be modified slightly to handle those! If folks are interested, I can do some more exploring this weekend.

@franklin-feingold
Copy link
Collaborator

Thank you @gallexi for raising Mergeable! I was just preparing a comment to propose that too! I found the pro's of Mergeable is the days old feature and that it can detect the 2 approvals. The one thing it is missing (as you mentioned) is the merge aspect. This would more than likely have to be used with another tool (like mergify). A potential workflow could be: Policy bot would provide the disapproval until a cue (like a comment) is given by Mergeable to change that to an approval and then the other tool (like mergify) can merge it. A concern is over enginnering is. What do we think about this or other suggestions? This is what I saw as a potential solution and gallexi if you would like to explore further, that sounds good, thank you!

Side note: The tool we have implemented for generating the changelog (github changelog generator) allows for PR titles to be changed after a merge and it will be represented correctly in the changelog on the next merged PR (this is the action that triggers a changelog build).

@gallexi
Copy link

gallexi commented Mar 14, 2019

I've looked more into Mergeable, as well as probot-scheduled-merge, and ended up coming upon semantic pull requests, which, despite not having anything to do with time on its own, I think could actually be pretty easily modified to fit the goal here!

I'll see if I can get a proof of concept up, but the general idea is that it would return a "pending" checks status until the latest commit (and, I assume we also want the PR itself) is older than 5 days. Then, it will return "success"!

Does that sound reasonable?

@sappelhoff
Copy link
Member

I am excited to see that proof of concept @gallexi! :-)

The semantic pull requests could also be used to automate what Chris suggested here regarding enforcement of PR Titles that will lead to a clean changelog

@franklin-feingold
Copy link
Collaborator

Thank you @gallexi for looking into this and I am also excited to see the proof concept!

@sappelhoff sappelhoff pinned this issue Mar 22, 2019
@sappelhoff sappelhoff unpinned this issue Mar 22, 2019
@franklin-feingold franklin-feingold pinned this issue Jul 22, 2019
@franklin-feingold franklin-feingold unpinned this issue Jul 26, 2019
@sappelhoff
Copy link
Member

GitHub now has "branch protection rules" where one can define CI jobs that must pass before a PR can be merged. (Note that admin overwrites are always possible).

Implementing the 5-day rule could work by writing a GitHub workflow that for a given PR checks the date of the most recent commit on that PR and compares it to the current date, passing only if the delta is >= 5 days.

Open points:

  • this CI check would initially only fail, which may be annoying
  • the CI check would have to run on a "scheduled" trigger (i.e., once every day or so), and I have never done that for PR CI jobs (only for those that run on branches that are part of the repo)

Perhaps there are better ideas, but I wanted to drop this here.

@Remi-Gau
Copy link
Collaborator

Probably only worth running for the PR that are not excluced from the change log.

Probably doable with the right sequence of "if".

@Remi-Gau Remi-Gau self-assigned this May 21, 2023
@Remi-Gau
Copy link
Collaborator

Remi-Gau commented May 21, 2023

Was playing around and at least this a script that checks all PRs and can sort those that match those criterias:

  • opened
  • not a draft
  • has no failure in CI
  • has no change requested
  • has 2 accepted reviews
  • last updated more than x days ago
  • has the exclude from changelog flag

I am not so sure we need this automation but if anyone wants to push that ball a little bit further.

from pathlib import Path
from warnings import warn

from datetime import datetime, timedelta, timezone
import requests
from rich import print

USERNAME = "Remi-Gau"

# may require a token if run often
TOKEN_FILE = Path("/home/remi/Documents/tokens/gh_read_pr_bids_spec.txt")

# repo to check
GH_USERNAME = "bids-standard"
GH_REPO = "bids-specification"

DEBUG = False

def authorize():
    TOKEN = None
    if TOKEN_FILE.exists():
        with open(TOKEN_FILE) as f:
            TOKEN = f.read().strip()
    return None if USERNAME is None or TOKEN is None else (USERNAME, TOKEN)

def repo_url(gh_username, gh_repo):
    """Return the URL of the repo."""
    base_url = "https://api.github.com/repos/"
    return f"{base_url}{gh_username}/{gh_repo}"

def get_list_of_prs(gh_username: str, gh_repo: str, auth=None):
    """List open PRs for a given repo.

    Parameters
    ----------
    gh_username : str
        _description_
    gh_repo : str
        _description_
    auth : None | tuple[str, str], optional
        _description_, by default None

    Returns
    -------
    _type_
        _description_
    """
    url = f"{repo_url(gh_username, gh_repo)}/pulls?per_page=100"
    response = requests.get(url, auth=auth)
    if response.status_code != 200:
        warn(f"Error {response.status_code}: {response.text}")
        return None
    return response.json()


def main():
    """"""
    pulls = get_list_of_prs(
        gh_username=GH_USERNAME, gh_repo=GH_REPO, auth=authorize()
    )

    for pr in pulls:

        if pr["state"] != "open" or pr["draft"]:
            continue

        print(f"PR {pr['number']}: {pr['title']}")

        if has_failure(pr):
            continue

        if has_positive_reviews(pr, threshold=2) and more_than_x_days_ago(pr, nb_days=5):
            print(pr.keys())
            print(f"  {pr['updated_at']}")


def has_positive_reviews(pr, threshold=2):
    url = f"{repo_url(GH_USERNAME, GH_REPO)}/pulls/{pr['number']}/reviews"

    response = requests.get(url, auth=authorize())
    if response.status_code != 200:
        warn(f"Error {response.status_code}: {response.text}")
        return False

    if len(response.json()) == 0:
        return False

    nb_approvals = 0
    changes_requested = False
    for review in response.json():
        if review['state'] == 'CHANGES_REQUESTED':
            print(f"  {review['user']['login']} - {review['state']}")
            changes_requested = True
        if review['state'] == 'APPROVED':
            print(f"  {review['user']['login']} - {review['state']}")
            nb_approvals += 1

    if nb_approvals > 0:
        print(f"  {pr['html_url']}")

    return nb_approvals >= threshold and not changes_requested
        


def has_failure(pr):
    """Check if a PR has a failure."""
    response = requests.get(pr['statuses_url'], auth=authorize())
    if response.status_code != 200:
        warn(f"Error {response.status_code}: {response.text}")
        return True

    value = False
    for status in response.json():
        if status['state'] == 'failure':
            print(f"  {status['state']}: {status['context']}")
            value = True
    return value

def more_than_x_days_ago(pr, nb_days=5):
    """Check if a PR has been updated more than 5 days ago."""
    updated_at = pr['updated_at']

    # Convert the string to a datetime object
    updated_datetime = datetime.fromisoformat(updated_at.replace("Z", "+00:00")).replace(tzinfo=timezone.utc)

    # Check if more than 5 days have elapsed
    elapsed_time = datetime.now(timezone.utc) - updated_datetime
    return elapsed_time > timedelta(days=nb_days)


if __name__ == "__main__":
    main()

@Remi-Gau
Copy link
Collaborator

@Remi-Gau Remi-Gau removed their assignment Jul 31, 2023
@sappelhoff
Copy link
Member

closing, as we have managed quite well >3 years without automating this rule. Feel free to protest / re-open if you disagree.

@sappelhoff sappelhoff mentioned this issue May 16, 2024
5 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request help wanted Extra attention is needed infrastructure
Projects
None yet
Development

No branches or pull requests

6 participants