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

Add support for remediation commits #147

Merged
merged 6 commits into from
Nov 16, 2021
Merged

Add support for remediation commits #147

merged 6 commits into from
Nov 16, 2021

Conversation

brianwarner
Copy link
Contributor

@brianwarner brianwarner commented Jan 16, 2021

This PR adds support for remediation commits and expands test coverage. In brief, a remediation commit is an additional way for projects to recognize valid, retroactive signoffs on commits which otherwise fail the Probot DCO checks.

Remediations come in two flavors. Individual remediation commits allow an author to fix one or more failing commits by pushing an additional, properly signed-off commit with specific text indicating they apply their signoff retroactively. Third-party remediations allow someone other than the author to do this.

About this PR

First and foremost the default Probot DCO behavior is unchanged, although the default remediation text and recommendations are modified (for reasons explained in the commit message). Unless a project explicitly enables remediation commits via their repo's .github/dco.yml file, it will continue to strictly enforce the DCO and only permit commits that explicitly include a proper Signed-off-by line.

The first commit (333a52f) expands and normalizes the default test cases. Running ./node_modules/jest/bin/jest.js --verbose now returns a structured view of what we're testing, organized into success and failure scenarios. This helps ensure the default behavior remains constant through the subsequent changes.

The rest of the commits incrementally add support for remediation commits. 5aecad9 modifies and consolidates the function that explains how to fix a failing PR, because it will be important later on. I also found a corner case in testing that makes me think it's better to just recommend all default fixes be rebases instead of amends. Basically if you push multiple commits and only one fails, and it's not the most recent commit, git commit --amend is going to fix the most recent commit and isn't going to fix the issue. Since both approaches required a force-push anyhow, it seemed less error prone to just recommend a rebase.

Next, 1a43157 adds full support for remediation commits. When a commit fails, the user gets specific copy-and-paste code blocks they can use to fix the problem. In addition the test cases are increased by 4x, because in addition to testing new functionality, they also repeat all of the previous tests to ensure enabling the config options does not mess with default results.

Finally 0696f05 updates the README.

Why add this feature

At the Linux Foundation we've heard feedback from some of our communities that DCO failures are trivial for some developers to remediate, but a barrier for others. In particular, some projects have expressed concern that one-time contributors are opening PRs through the GitHub web UI, are getting blocked because they don't know to add a signoff, and are simply abandoning their contributions rather than set up a local git environment, amend their patches, and push fixes. Currently the GitHub UI does not permit authors to amend commit messages, so this was conceived as a lighter weight workaround.

In addition, this enables targeted remediations. For example, if a branch contains multiple authors, a rebase will add an author's signoff to all commits, even ones they didn't author. This may not be desirable behavior. Both individual and third-party remediations allow an author to retroactively sign off on only the commits they want to fix. This could also be used when doing a bulk remediation on a project which adopted the DCO after being open sourced.

Finally, the goal was to establish standard text that could be replicated and understood by other tools. I specifically avoided an approach that relies upon GitHub tooling (such as signing off in the comment stream or on the PR itself) because this approach embeds the remediation directly in the git log, making it both portable and permanent.

Due diligence

I recognize this is a significant addition in functionality. Over the past year I've reviewed the concept with a number of open source legal folks and some of the large projects hosted at the Linux Foundation. The feedback has been generally positive, particularly given that it can be enabled on a project-by-project basis.

Next steps

These commits attempt to preserve the existing code style to generate a minimal diff, and make it easier to see what has changed. If merged, it would make sense to follow up with a linted version.

Thanks!


View rendered README.md

Copy link
Contributor

@gr2m gr2m left a comment

Choose a reason for hiding this comment

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

Changes look good to me overall. I'm just concerned that it adds quite some complexity, I am worried about the maintenance of the app. As you use the app at the Linux Foundation, I wonder if you or someone else from your team(s) could help out maintaining DCO, and especially the advanced features added by this PR?

test/dco.test.js Outdated Show resolved Hide resolved
Copy link
Contributor

@hiimbex hiimbex left a comment

Choose a reason for hiding this comment

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

So I tested current functionality and feel pretty comfortable merging considering this is an opt in feature, especially with @brianwarner offering to help maintain this feature.

I skimmed the actual logic changes but don't really care to nitpick- would love a follow up styling/refactoring PR - rereading this code hurts my brain 🙈

I did try to test the new code path, but can't quite get the scenario correct? Left an inline comment where i got stuck.

The tests look fantastic - really appreciate the thoroughness.


let rebaseWarning = ''

if (allowRemediationCommits.individual || allowRemediationCommits.thirdparty) {
Copy link
Contributor

Choose a reason for hiding this comment

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

When testing the feature, I never made it into this if statement - here's the yaml file i used: https://github.com/hiimbex/Test/blob/master/.github/dco.yaml & the test PR i created: hiimbex/Test#4 - totally could be something wrong in how I'm testing, so let me know, but this scenario was my interpretation from the docs.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hmm, this is odd. I've been staring at this and must be missing something obvious... it was working earlier in development and appears to have stopped now. It's like it isn't reading the dco.yml file correctly and is always defaulting to the false values for both. Does anything jump out around line 20?

I guess this wasn't surfaced in the dco.js tests because those two variables are defined independent of what's in dco.yml for each test. I'm coming up against the limits of my knowledge of jest here, and suspect this would have been detected had there been appropriate snapshot tests that include permutations of dco.js. I'll admit I don't know how to do this aside from literally changing the default value in index.js, do you have any suggestions?

}

function getSignoffs (commit) {
const regex = /^Signed-off-by: (.*) <(.*)>$/img
function getSignoffs (comparisonCommit, comparisonSha, allCommits, allowRemediationCommits) {
Copy link
Contributor

Choose a reason for hiding this comment

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

I find the naming comparison a bit confusing - this is referring to the single commit that's currently being processed correct? esp when we get into this area it gets pretty hard to parse.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, that's correct and it's a good point. It is confusing. currentCommit and currentSha might be better? As in, this is the current commit we're processing in an attempt to find valid remediation text in all other commits.

@brianwarner
Copy link
Contributor Author

I have to admit, I'm totally stuck on this. For some reason it's no longer reading the config file, and I'm not sure what I'm doing wrong. @hiimbex or @gr2m, it's probably some completely naive mistake on my part... Do you have any suggestions why this particular line would not be reading an optional config file at .github/dco.yml?

@gr2m
Copy link
Contributor

gr2m commented Mar 16, 2021

I don't know out of hand, sorry, I'd have to take a longer look, and I don't have the time right now, sorry.

Do you see any requests happening if you set LOG_LEVEL to debug? Also the probot version used in the app is a beta version, it woudl be good to upgrade that to the latest stable version, as there were several fixes since the beta

@brianwarner
Copy link
Contributor Author

Hmm, I upgraded all packages, and now my test instance is crashing on deploy at heroku. I switched back to master, verified it deploys properly before running npm upgrade (it does), and verified that it crashes after going from 11.0.0-beta3 to 11.1.0 (it does, unfortunately). It doesn't even get to the point of debug logging. The odd thing is that it builds and runs fine locally.

I totally understand the lack of time, and unfortunately it looks like I'm finding more problems than I'm solving. Looks like I have some homework to do.

For what it's worth, here's the error log from heroku:

2021-03-16T15:46:49.443667+00:00 heroku[web.1]: State changed from crashed to starting
2021-03-16T15:46:53.281745+00:00 heroku[web.1]: Starting process with command `npm start`
2021-03-16T15:46:56.967922+00:00 app[web.1]:
2021-03-16T15:46:56.967936+00:00 app[web.1]: > dco@1.0.0 start
2021-03-16T15:46:56.967936+00:00 app[web.1]: > probot run ./index.js
2021-03-16T15:46:56.967937+00:00 app[web.1]:
2021-03-16T15:46:58.905343+00:00 app[web.1]: INFO (server): Running Probot v11.1.0 (Node.js: v15.11.0)
2021-03-16T15:46:58.911452+00:00 app[web.1]: INFO (server): Listening on http://localhost:29480
2021-03-16T15:46:58.916887+00:00 app[web.1]: /app/index.js:8
2021-03-16T15:46:58.916888+00:00 app[web.1]: app.on(
2021-03-16T15:46:58.916889+00:00 app[web.1]: ^
2021-03-16T15:46:58.916889+00:00 app[web.1]:
2021-03-16T15:46:58.916890+00:00 app[web.1]: TypeError: Cannot read property 'on' of undefined
2021-03-16T15:46:58.916897+00:00 app[web.1]: at module.exports (/app/index.js:8:7)
2021-03-16T15:46:58.916898+00:00 app[web.1]: at Server.load (/app/node_modules/probot/lib/server/server.js:49:15)
2021-03-16T15:46:58.916898+00:00 app[web.1]: at combinedApps (/app/node_modules/probot/lib/run.js:87:20)
2021-03-16T15:46:58.916899+00:00 app[web.1]: at async Server.load (/app/node_modules/probot/lib/server/server.js:49:9)
2021-03-16T15:46:58.916899+00:00 app[web.1]: at async Object.run (/app/node_modules/probot/lib/run.js:90:9)
2021-03-16T15:46:59.067203+00:00 heroku[web.1]: Process exited with status 1
2021-03-16T15:46:59.157881+00:00 heroku[web.1]: State changed from starting to crashed

@brianwarner
Copy link
Contributor Author

Well, I figured one thing out... the config file is read only if it was present in the repo prior to where the PR was forked. So if, for example, you make some changes that result in a dco failure, then update .github/dco.yml in the primary branch to enable the new paths, and just close/reopen the original PR, it'll not detect the changes to the config file and looks like it isn't working.

@hiimbex, I was able to use the new functionality by doing the following:

  1. Make a change without signoff and open a PR, check that it fails (verifying the default case)
  2. Commit a .github.com/dco.yml file to the primary branch that enables individual or individual+third party remediations
  3. Make a new change without signoff and open a fresh PR, and verify that it provides individual remediations as an option

Taking a step back, I think this makes logical sense. Enabling/disabling remediation commits is a policy change that likely shouldn't be applied retroactively. And if someone opened a PR prior to enabling remediation commits, they should be able to rebase on the main branch.

@gr2m
Copy link
Contributor

gr2m commented Mar 31, 2021

can we close this PR then? Can we somehow improve the docs to clarify the behavior?

@brianwarner
Copy link
Contributor Author

Hi @gr2m, I'd like to still keep this one open for consideration if possible, please. This feature would be really helpful in some of our projects.

@bnb
Copy link

bnb commented Aug 17, 2021

@gr2m would you be open to adding CODEOWNERS for just the features being added here?

@gr2m
Copy link
Contributor

gr2m commented Aug 17, 2021

do you mean to move the new features implemented here to separate files and then use CODEOWNERS to enforce reviews / communicate ownership for these files? Sounds good to me

@gr2m
Copy link
Contributor

gr2m commented Oct 28, 2021

Hi everyone! We created a DCO team with admin access to this repository and invited @brianwarner and @ryjones to the team.

The pull request is stellar and with a proper DCO maintenance team in place we don't see a reason not to go ahead and merge it. I can assist with any technical questions and problems from the perspective of the Probot framework.

It might be a good idea to deploy the version of this pull request to a test environment, in case you haven't done so yet. I can help with that as well. Please let me know how you'd like to proceed.

@ryjones
Copy link

ryjones commented Oct 28, 2021

@gr2m I'm willing to start clicking "merge" but yes let's talk about how to test this :)

@gr2m
Copy link
Contributor

gr2m commented Nov 9, 2021

I've looked at the PR and current state of DCO. I'd like to address a few things before merging to master and deploying

  • Upgrade Probot v11-beta.3 to latest v11 (we can upgrade to v12 later)
  • Increase test coverage. I like to have 100% test coverage, especially for Open Source projects, to accept changes with bigger confidence and speed

For simpler collaboration, I suggest I merge this PR into a local branch, and then start a PR against that branch which addresses the points above.

Once we feel confident I'd do a preview deployment so we can test the changes before deploying them to all users of the DCO app.

This commit makes minimial changes to Probot DCO itself, and does not add any features aside from
adding the author's email to the commit object that gets returned on a failed check, and allowing
properly signed-off commits with trailing whitespace.

The goals of this commit are:
* Expand text coverage (by approx 2x)
* Standardize how tests are named
* Organize the tests using `describe()` (try running jest with the --verbose flag!)
* Lay the groundwork for the next commits, which add significant functionality

By expanding test coverage and adding more corner cases, it will help ensure that the forthcoming
features don't inadvertently change default functionality.

Signed-off-by: Brian Warner <brian@bdwarner.com>
This commit moves some things around in preparation for adding support for remediation commits. It
does a number of things which will be used in subsequent commits:

* Adds config file support for remediation commits, which are disabled by default.
* Updates the DCO check function call to be aware of remediation commits.
* Refactors signoff failure feedback into a single function, which will be subsequently expanded.
* Update the snapshot to reflect the updated text.

In addition, this commit updates the default feedback text. While testing I noted that there could
be situations where amending a commit would not fix the issue (e.g., if the failing commit was not
at the tip). Since both amend and rebase regenerate the hash and require a force push, I
consolidated feedback for both into one common text.

Signed-off-by: Brian Warner <brian@bdwarner.com>
This commit adds support for individual and third-party remediation commits. These are disabled
unless explicitly enabled by the end user to preserve the default, more conservative functionality.

* What is a remediation commit?

In brief, a remediation commit is a way to achieve DCO signoffs without amending the original
commit. Remediation commits were proposed upon feedback from open source communities that PRs were
failing or being abandoned because not everybody checks their code out to a local git environment.
In particular, the GitHub UI does not currently support amending a commit log, so PRs which failed
the Probot DCO check were being abandoned by committers. Remediation commits are proposed as an
improvement because they can be executed entirely within the GitHub web UI, while still being a part
of a project's immutable and portable git history.

* Why would I use this when I can amend commits?

The process of amending commits works well for single contributors working alone in a branch, but it
has challenges when there are multiple contributors or when rebasing the branch would break others'
workflows. For example, `git rebase HEAD~10 --signoff` adds the author's signoff to all 10 prior
commits, whether they authored them or not.

The advantage of this approach is that it allows contributors to pinpoint the exact commits for
which they need to add a signoff, even if they've already been merged.

* How does a remediation commit work?

There are two types of remediation commits: individual and third-party.

For an individual remediation, the original author pushes a second commit with specific text in the
commit log indicating they are adding their Signed-off-by line retroactively. Only the original
author can provide an individual remedation.

For a third-party remediation, someone else pushes a second commit with specific text in the commit
log indicating they are adding their Signed-off-by line in place of the author. This is intended for
situations where the original author is not available and where another can make the DCO commitment
in their place (e.g., an employer).

* Why is this not turned on by default?

The default Probot DCO behavior mirrors the workflow of the Linux kernel, where an author who
forgets a DCO signoff must amend their commits and resubmit them. This is a well-known process and
a broadly adopted requirement, and the intent is not to change how Probot works for projects that
have adopted it under these expectations.

* How do I enable this?

Projects can enable remediation commits on a repo-by-repo basis by adding a `dco.yml` config file in
their `/.github` directory with the following lines:

  allowRemediationCommits:
    individual: true
    thirdParty: true

Note that individual remediations must be enabled for thirdParty remediations to work.

* When should I enable this?

You should have a conversation with your project leadership to determine "if remediation commits are
sufficient to be considered a DCO Sign-off." There may be different answers for different projects.
Contact your legal counsel if you aren't sure what this means.

* How does a contributor know what remediation text to use?

Much as the DCO signoff text is standardized ("Signed-off-by: Name <email>"), the individual and
third-party remediations are also intended to be standardized. This will allow other tools to search
for the text and come to the same conclusion whether a commit has been correctly signed off. When
the check runs, it will show the exact text the author or a third-party will need to copy into the
commit message to constitute a valid remediation commit.

In general, the standard signoff texts are:
Default: Signed-off-by: Brian Warner <brian@bdwarner.com>
Individual: I, Brian Warner <brian@bdwarner.com>, hereby add my Signed-off-by to this commit: <SHA>
Third-party: On behalf of Not Brian <notbrian@notbdwarner.com>, I, Brian Warner <brian@bdwarner.com>, hereby add my Signed-off-by to this commit: <SHA>

---

This commit includes the following:

* Support for individual remediation commits
* Support for third-party remediation commits
* Tests that verify the new behavior
* Tests that verify the default behavior is unchanged when remediation commits are enabled
* An additional warning about how rebasing can break workflows if remediation is available

Signed-off-by: Brian Warner <brian@bdwarner.com>
Add a brief description of what remediation commits do, and how they differ from the default mode of
operation.

Signed-off-by: Brian Warner <brian@bdwarner.com>
Signed-off-by: Brian Warner <brian@bdwarner.com>
Signed-off-by: Brian Warner <brian@bdwarner.com>
@gr2m gr2m changed the base branch from master to remediation-commits November 9, 2021 19:09
allowRemediationCommits:
individual: true
thirdParty: true
```
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we add instructions on how to add remediation commits using git and the GitHub Web UI? I know you add instructions to the check runs which is great, but I think we should document it here as well, what do you think?

If I understand it correctly, ideally remediation commits would be empty commits with the correct messages, but there is no way to add empty commits using the GitHub Web UI as far as I know.

Comment on lines +37 to +39
allowRemediationCommits:
individual: true
thirdParty: true
Copy link
Contributor

Choose a reason for hiding this comment

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

I think setting thirdParty: true will set individual: true implicitly, setting both is no different to just setting thirdParty: true

https://github.com/brianwarner/dco/blob/eb53e5d32ce5d48b1467a581ed0ea404dc0a349c/index.js#L186

If that's the case, how about we change the setting to

allowRemediationCommits: individual # or: thirdParty


let rebaseWarning = ''

if (allowRemediationCommits.individual || allowRemediationCommits.thirdparty) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
if (allowRemediationCommits.individual || allowRemediationCommits.thirdparty) {
if (allowRemediationCommits.individual || allowRemediationCommits.thirdParty) {

Copy link
Contributor

Choose a reason for hiding this comment

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

I caught this as I was adding more tests. No need to change the current PR, let's merge this into a local branch (not master) then I'll send a PR with my changes against it

// Draft the magic DCO remediation commit text for the author
// returnMessage = returnMessage + `Retroactive-signed-off-by: ${commit.author} <${commit.email}> ${commit.sha}\n`
returnMessage = returnMessage + `On behalf of ${commit.author} <${commit.email}>, I, YOUR_NAME <YOUR_EMAIL>, hereby add my Signed-off-by to this commit: ${commit.sha}\n`
console.log(commit.sha)
Copy link
Contributor

Choose a reason for hiding this comment

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

I asume this is a leftover console.log()? I'll remove it in my follow up PR

@gr2m gr2m merged commit 9f42879 into dcoapp:remediation-commits Nov 16, 2021
gr2m pushed a commit that referenced this pull request Jan 18, 2022
Signed-off-by: Brian Warner <brian@bdwarner.com>
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.

5 participants