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

Ensure GitHub status checks have passed #289

Closed
wants to merge 57 commits into from

Conversation

sonicdoe
Copy link
Contributor

@sonicdoe sonicdoe commented Jul 26, 2018

Ensures GitHub status checks have passed using the Statuses API. Resolves #53.

This is a really quick implementation to gather feedback early. Some things that are missing or need to be discussed:

  • Skip if there’s no repository URL
  • Skip if the repository is not hosted on GitHub
  • Link to failed status checks (using terminal-link)
  • Handle unpushed commits (see Ensure GitHub status checks have passed #289 (comment))
  • Time out request loop
  • Handle API rate limits
  • Provide feedback while looping
  • Skip GitHub status checks (--no-checks)
  • Reword task title (I don’t really like the sound of “Check … checks”)
  • Move task to be a top-level task

@sindresorhus
Copy link
Owner

Thanks for starting this 🙌

@sindresorhus
Copy link
Owner

I agree with your list of missing things.

Link to the failed status check

Use terminal-link.

@sindresorhus
Copy link
Owner

Fail if commit hasn’t been pushed to GitHub

I often do some local commits. Usually some nitpicks before doing a release. And I then run np and let np push the commits to GitHub. Would be nice if this was handled and it would just wait for the status check of these new commits.

@sindresorhus
Copy link
Owner

Reorganize task (perhaps this should be a top-level task?)

👍

lib/prerequisite.js Outdated Show resolved Hide resolved
lib/prerequisite.js Outdated Show resolved Hide resolved
@sonicdoe sonicdoe changed the title [WIP] Check GitHub status checks [WIP] Ensure GitHub status checks have passed Oct 24, 2018
@sonicdoe sonicdoe force-pushed the github-status-check branch 2 times, most recently from 4a29c15 to d5c0f80 Compare October 24, 2018 20:29
@sonicdoe sonicdoe force-pushed the github-status-check branch 2 times, most recently from ef74c8d to bc8d011 Compare October 27, 2018 09:33
@sonicdoe
Copy link
Contributor Author

sonicdoe commented Oct 27, 2018

It’s getting somewhere 🙌

The checklist is now done but I still need to thoroughly test every case. Nevertheless, I think this is ready for another code review. I hope the commits along with my intentions are easy to follow. If there’s anything unclear, let me know.

Edit: I’ve now manually tested quite a few cases, such as commits with pending checks, commits with multiple failed checks, rate limit exceptions, and timeouts.

- If there’s only one check, GitHub shows “Failure: ” and the check’s description
- If there are multiple checks and only some failed, GitHub shows “Some checks were not successful” and a list of all checks
- If there are multiple checks and all failed, GitHub shows “All checks have failed” and a list of all checks
cli.js Outdated Show resolved Hide resolved
lib/gh-checks.js Outdated Show resolved Hide resolved
lib/gh-checks.js Outdated Show resolved Hide resolved
lib/gh-checks.js Outdated Show resolved Hide resolved
lib/gh-checks.js Outdated Show resolved Hide resolved
index.js Outdated Show resolved Hide resolved
index.js Outdated Show resolved Hide resolved
index.js Outdated
title: 'Ensure GitHub checks have passed',
skip: () => {
if (!pkg.repository) {
return 'No repository specified';
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No repo specified in package.json

?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you think about “No repository specified in package.json”? The package.json field is also called repository so I’m not sure whether we should abbreviate here.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

“No repository specified in package.json”? The package.json field is also called repository so I’m not sure whether we should abbreviate here.

👍

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done: c12e272.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should np fallback to getting the repo URL from git itself? I feel like requiring pkg.repository is just needless busy work.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I’ve had the same thought at one point. I don’t have particularly strong opinions in either direction.

On one hand, requiring pkg.repository encourages best practices (for example, npmjs.com will display a link to the repository if the field is specified) and gives us higher assurance that it actually specifies the package’s repository (whereas the Git remote might be a fork). On the other hand, this task already kind of expects the Git remote to be the upstream repository (since we git push before querying the GitHub API).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's an interesting thought. If I'm on a fork and running np, I'm probably intentionally trying to publish some hacky thing I did, perhaps to a scoped package under my own personal namespace. I'm more likely to have a PR open on the pkg.repository (which I probably haven't updated and where CI checks are probably running) than to have CI setup on my fork. I don't think I have ever set up CI on a fork, though major projects obviously would. 🤔

That being said, I still think if pkg.repository isn't defined at all, it might be worth having some kind of fallback. Just "thinking out loud" here.

// It might take a bit for the status checks to be created.
// Even if, it probably takes at least a few seconds for them to pass.
task.title = `${title} ${chalk.yellow('(waiting for checks to start…)')}`;
await delay(THIRTY_SECONDS);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't 10-15s suffice in this case?
Also, is this really needed? Won't pWaitFor do this for us anyways?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't 10-15s suffice in this case?

Probably. I just sticked to the existing delay to keep it simple. If there are actually any status checks, I’d expect them to take more than 30 seconds to complete for most projects anyway.

Also, is this really needed? Won't pWaitFor do this for us anyways?

If we immediately call GitHub’s status API, total_count might still be zero and we’d skip the task with “No status checks found”. On the flip side, we can’t retry in that case because commits without any status checks would be caught in a loop then (since the state will always be pending).

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't 10-15s suffice in this case?

I agree. 30 seconds is too long.

It might take a bit for the status checks to be created.

This sounds like a GitHub bug. Why wouldn't they be created right away?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree. 30 seconds is too long.

Changed to 10 seconds in c704b6c.

This sounds like a GitHub bug. Why wouldn't they be created right away?

As far as I understand, GitHub can’t know whether a commit will result in status checks being created. For example, GitHub only notifies Travis CI of Git pushes and it’s then Travis CI’s call whether to create any status checks (which it might not need to, depending on your settings).

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. What about GitHub Actions? I assume GitHub would know then?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GitHub could but I still think there would be a slight delay. According to GitHub Help, GitHub Actions uses the Checks API to output statuses. Therefore, I think the Statuses API would still return zero status checks in the time frame between the commit having been pushed and the workflow starting to run.

source/util.js Outdated Show resolved Hide resolved
@sindresorhus
Copy link
Owner

@sonicdoe Is this done? If so, I think it would be beneficial to merge it and get it out there so people can try it out and report any issues.

@sonicdoe
Copy link
Contributor Author

sonicdoe commented Jun 7, 2020

@sindresorhus Sounds good, I’m fine with giving it a go 🙂

@sindresorhus
Copy link
Owner

I just tried this on https://github.com/sindresorhus/is-interactive after pushing a commit, but it skipped the task:

  ↓ Ensuring GitHub status checks have passed (waiting for checks to start…) [skipped]
    → No status checks found

However, there clearly is a status check:

Screenshot 2020-06-29 at 15 40 16

@sonicdoe
Copy link
Contributor Author

It looks like it took over ten seconds for the status check to be created. I don’t think GitHub exposes the push time so it’s difficult to say for sure. I can only see that the commit was committed at 07:37:04 and the Travis CI build started at 07:39:06.

If that’s the case, I’m not sure how to best resolve this. We don’t know when a check will be created and we can’t know in advance whether any checks will be created at all 😕 If it’s any improvement, we could check whether the latest commit (before pushing new ones) has checks and use that as an indicator.

@sholladay
Copy link
Contributor

If I understand the code correctly, it currently delays 10 seconds, then tries to check the status, and if totalCount happens to be zero at that time, it gives up and skips, without retrying. That seems pretty fragile.

Here's how I would approach this:

  1. Delay ten seconds
  2. Predict whether a status check is likely to eventually exist, by:
  3. Check the commit status and if totalCount === 0, then:
    • If step 2 found previous status checks / branch protection contexts, retry every ten seconds, up to a limit of five minutes
    • If step 2 errored or found no status checks / branch protection contexts, retry every ten seconds, up to a limit of one minute

@sonicdoe
Copy link
Contributor Author

Interestingly, Get status checks protection is only available to authenticated users (see this topic). Fortunately, Get a branch provides enough information in the protection key. For example, /repos/sholladay/pogo/branches/master lists two status checks: “Travis CI - Branch” and “Travis CI - Pull Request”.

In any case, I’m not sure how widespread protected branches are. For example, neither lodash/lodash, nor facebook/react, nor chalk/chalk list required status checks. Most importantly, sindresorhus/is-interactive doesn’t have any.

@sholladay
Copy link
Contributor

That's why retries are so important. It doesn't currently retry when totalCount is 0, but it should. My suggestion is to retry up to one minute when there are no branch protection rules.

I do think that more people would enable branch protection if np gave them a good reason to do so.

@sonicdoe
Copy link
Contributor Author

sonicdoe commented Jul 1, 2020

We’ve actually had a delay of 30 seconds before (see this conversation). I guess it will always be a balance between not missing any status checks and annoying people who don’t use them at all.

I do think that more people would enable branch protection if np gave them a good reason to do so.

How would retrying up to one minute when there are no branch protection rules motivate people to enable them? People who don’t use status checks couldn’t enable protection and therefore would always have to wait a whole minute. On the flip side, people who do use status checks wouldn’t really gain much from enabling protection because they’d have to wait for their status checks to appear in both cases. Granted, the second group would have the minor benefit of np failing in case their status checks don’t appear after a minute.

@sholladay
Copy link
Contributor

How would retrying up to one minute when there are no branch protection rules motivate people to enable them?

It's about reliability. Take Sindre, for example. As you mentioned, he doesn't currently use branch protection. With the algorithm I proposed, it'll still work for his repos as-is, but if there's an unusually high delay, as happens occasionally (on bad days I've seen it take a couple of minutes to create the status), then np would more eagerly skip this step compared to if he enabled branch protection. The docs can mention this as a means to improve reliability.

Additionally, I suspect that a lot of people may just not be aware of the branch protection feature and its ability to guarantee a status check has passed. It's not exactly front and center in the GitHub UI. There are good reasons to enable it that have nothing to do with np, so mentioning it in the docs would be a good thing for the community.

People who don’t use status checks couldn’t enable protection and therefore would always have to wait a whole minute.

No, not always. I'm not proposing a one minute delay. It's a timeout. It's only a whole minute in the worst case.

@sonicdoe
Copy link
Contributor Author

sonicdoe commented Jul 3, 2020

Thanks for elaborating. I’d even go so far as to fail if np can’t find any status checks and branch protection is enabled. In some sense, this matches the behavior on GitHub where pull requests can’t be merged if status checks haven’t reported any status.

No, not always. I'm not proposing a one minute delay. It's a timeout. It's only a whole minute in the worst case.

I don’t think I follow. If you don’t use status checks at all, in which case would your proposed algorithm skip sooner than one minute?

Base automatically changed from master to main January 23, 2021 17:55
@sindresorhus
Copy link
Owner

Is anyone interested in pushing this forward? This is not something I personally need, so I don't really have a lot of energy to make decisions and push this forward.

@sindresorhus
Copy link
Owner

I'm unfortunately going to close this as it doesn't seem to go anywhere: #53 (comment)

@sonicdoe
Copy link
Contributor Author

sonicdoe commented Jun 4, 2021

No worries, thanks for everything so far! The fact that status checks are created asynchronously, which I think is the biggest unresolved issue, is certainly unfortunate.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Ensure CI has passed
4 participants