diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index a4d0158..ffaf508 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -49,6 +49,7 @@ Fixes # - [ ] I have made corresponding changes to the documentation - [ ] My changes generate no new warnings - [ ] New and existing unit tests pass locally with my changes +- [ ] All commits are signed off (`git commit -s`) — see [DCO](../CONTRIBUTING.md#developer-certificate-of-origin-dco) ## Additional Notes diff --git a/.github/dco.yml b/.github/dco.yml new file mode 100644 index 0000000..c91a164 --- /dev/null +++ b/.github/dco.yml @@ -0,0 +1,17 @@ +# Author: Sachindu Nethmin +# GitHub: https://github.com/Sachindu-Nethmin +# Created: 2026-02-21 +# Last Modified: 2026-02-21 + +# DCO Bot Configuration +# Docs: https://github.com/probot/dco + +# Require DCO sign-off on commits +require: + members: true + +# Allow remediation commits so contributors can retroactively sign off +# without rebasing their entire PR. +allowRemediationCommits: + individual: true + thirdParty: true diff --git a/.github/workflows/dco-remediation.yml b/.github/workflows/dco-remediation.yml new file mode 100644 index 0000000..fcc6672 --- /dev/null +++ b/.github/workflows/dco-remediation.yml @@ -0,0 +1,204 @@ +# Author: Sachindu Nethmin +# GitHub: https://github.com/Sachindu-Nethmin +# Created: 2026-02-21 +# Last Modified: 2026-02-21 + +# ============================================================================= +# DCO Comment-Based Remediation (Two-Step Flow) +# ============================================================================= +# Step 1: When the DCO status check fails on a PR, this workflow posts a +# comment asking the author if they want to sign the DCO. +# Step 2: When the author replies with the magic phrase, the workflow pushes +# a signed-off remediation commit on their behalf. +# +# Trigger phrase: +# "I have read the DCO document and I hereby sign the DCO" +# ============================================================================= + +name: DCO Remediation + +on: + # Step 1: Fires when any commit status (including DCO) is updated + status: + # Step 2: Fires when someone comments on a PR + issue_comment: + types: [created] + +permissions: + contents: write + pull-requests: write + statuses: read + +jobs: + # --------------------------------------------------------------- + # Step 1: DCO check failed → ask the PR author to sign + # --------------------------------------------------------------- + ask-to-sign: + name: Ask Author to Sign DCO + runs-on: ubuntu-latest + if: > + github.event_name == 'status' && + github.event.state == 'failure' && + contains(github.event.context, 'dco') + steps: + - name: Find associated pull requests + id: find-pr + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + SHA="${{ github.event.commit.sha }}" + # Find open PRs whose head SHA matches the failed commit + PR_NUMBERS=$(gh api "repos/${{ github.repository }}/pulls?state=open&per_page=100" \ + --jq "[.[] | select(.head.sha == \"$SHA\")] | .[].number") + + if [ -z "$PR_NUMBERS" ]; then + echo "No open PRs found for SHA $SHA" + echo "found=false" >> "$GITHUB_OUTPUT" + exit 0 + fi + echo "pr_numbers=$PR_NUMBERS" >> "$GITHUB_OUTPUT" + echo "found=true" >> "$GITHUB_OUTPUT" + + - name: Post sign-off prompt + if: steps.find-pr.outputs.found == 'true' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + MARKER="" + for PR_NUM in ${{ steps.find-pr.outputs.pr_numbers }}; do + # Check if we already posted the prompt (avoid duplicates) + EXISTING=$(gh api "repos/${{ github.repository }}/issues/$PR_NUM/comments" \ + --jq "[.[] | select(.body | contains(\"$MARKER\"))] | length") + if [ "$EXISTING" -gt 0 ]; then + echo "Prompt already posted on PR #$PR_NUM, skipping." + continue + fi + + gh pr comment "$PR_NUM" \ + --repo "${{ github.repository }}" \ + --body "$MARKER + ## ⚠️ DCO Sign-Off Required + + It looks like one or more commits in this PR are missing a \`Signed-off-by\` line. + + If you agree to the [Developer Certificate of Origin](https://developercertificate.org/), please reply with the following **exact phrase**: + + > I have read the DCO document and I hereby sign the DCO + + A remediation commit will be pushed to your branch automatically. + + Alternatively, you can sign off manually: \`git commit --allow-empty -s -m \"chore: DCO sign-off\" && git push\`" + done + + # --------------------------------------------------------------- + # Step 2: Author replied with the phrase → push remediation commit + # --------------------------------------------------------------- + sign-and-push: + name: Push DCO Remediation Commit + runs-on: ubuntu-latest + if: > + github.event_name == 'issue_comment' && + github.event.issue.pull_request && + contains(github.event.comment.body, 'I have read the DCO document and I hereby sign the DCO') + steps: + - name: Get PR details + id: pr + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + PR_JSON=$(gh api "repos/${{ github.repository }}/pulls/${{ github.event.issue.number }}") + HEAD_REF=$(echo "$PR_JSON" | jq -r '.head.ref') + HEAD_REPO=$(echo "$PR_JSON" | jq -r '.head.repo.full_name') + PR_AUTHOR=$(echo "$PR_JSON" | jq -r '.user.login') + USER_ID=$(echo "$PR_JSON" | jq -r '.user.id') + echo "head_ref=$HEAD_REF" >> "$GITHUB_OUTPUT" + echo "head_repo=$HEAD_REPO" >> "$GITHUB_OUTPUT" + echo "pr_author=$PR_AUTHOR" >> "$GITHUB_OUTPUT" + echo "user_id=$USER_ID" >> "$GITHUB_OUTPUT" + + # Detect forked PRs — GITHUB_TOKEN cannot push to forks + if [ "$HEAD_REPO" != "${{ github.repository }}" ]; then + echo "is_fork=true" >> "$GITHUB_OUTPUT" + echo "Warning: PR is from a fork ($HEAD_REPO). Cannot auto-push." + else + echo "is_fork=false" >> "$GITHUB_OUTPUT" + fi + + - name: Verify commenter is PR author + id: verify + run: | + COMMENTER="${{ github.event.comment.user.login }}" + PR_AUTHOR="${{ steps.pr.outputs.pr_author }}" + if [ "$COMMENTER" != "$PR_AUTHOR" ]; then + echo "⚠️ Commenter ($COMMENTER) is not the PR author ($PR_AUTHOR). Skipping." + echo "authorized=false" >> "$GITHUB_OUTPUT" + else + echo "✅ Commenter is the PR author." + echo "authorized=true" >> "$GITHUB_OUTPUT" + fi + + - name: Checkout PR branch + if: steps.verify.outputs.authorized == 'true' && steps.pr.outputs.is_fork == 'false' + uses: actions/checkout@v4 + with: + ref: ${{ steps.pr.outputs.head_ref }} + repository: ${{ steps.pr.outputs.head_repo }} + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Push remediation commit + if: steps.verify.outputs.authorized == 'true' && steps.pr.outputs.is_fork == 'false' + env: + AUTHOR_NAME: ${{ github.event.comment.user.login }} + AUTHOR_ID: ${{ steps.pr.outputs.user_id }} + run: | + git config user.name "$AUTHOR_NAME" + git config user.email "${AUTHOR_ID}+${AUTHOR_NAME}@users.noreply.github.com" + + git commit --allow-empty \ + -s \ + -m "chore: DCO remediation for $AUTHOR_NAME + + I have read the DCO document and I hereby sign the DCO + for all past and future contributions." + + git push + + - name: Acknowledge remediation + if: steps.verify.outputs.authorized == 'true' && steps.pr.outputs.is_fork == 'false' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + gh pr comment "${{ github.event.issue.number }}" \ + --repo "${{ github.repository }}" \ + --body "✅ **DCO Remediation Complete** + + A signed-off remediation commit has been pushed to this PR on behalf of @${{ github.event.comment.user.login }}. + + The DCO check should pass shortly. Thank you for signing off! 🎉" + + - name: Reject non-author + if: steps.verify.outputs.authorized != 'true' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + gh pr comment "${{ github.event.issue.number }}" \ + --repo "${{ github.repository }}" \ + --body "⚠️ Only the PR author can sign the DCO for this pull request. @${{ github.event.comment.user.login }} is not the author of this PR." + + - name: Guide forked PR author + if: steps.verify.outputs.authorized == 'true' && steps.pr.outputs.is_fork == 'true' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + gh pr comment "${{ github.event.issue.number }}" \ + --repo "${{ github.repository }}" \ + --body "👋 Thanks for signing the DCO, @${{ github.event.comment.user.login }}! + + Because this PR is from a **fork**, I cannot push the remediation commit automatically. Please run the following in your local branch: + + \`\`\`bash + git commit --allow-empty -s -m \"chore: DCO sign-off\" + git push + \`\`\` + + The DCO check will pass once the signed-off commit is pushed. 🎉" diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d12b56b..a2a0cf9 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -5,6 +5,7 @@ First off, thank you for considering contributing to Simili! 🎉 ## Table of Contents - [Code of Conduct](#code-of-conduct) +- [Developer Certificate of Origin (DCO)](#developer-certificate-of-origin-dco) - [Getting Started](#getting-started) - [How Can I Contribute?](#how-can-i-contribute) - [Development Setup](#development-setup) @@ -15,6 +16,53 @@ First off, thank you for considering contributing to Simili! 🎉 This project adheres to a [Code of Conduct](CODE_OF_CONDUCT.md). By participating, you are expected to uphold this code. +## Developer Certificate of Origin (DCO) + +This project uses the [Developer Certificate of Origin (DCO)](https://developercertificate.org/) to ensure that all contributions are properly licensed. The DCO is enforced by the [DCO bot](https://github.com/apps/dco) on every pull request. + +### Signing Off Your Commits + +Every commit in your PR **must** include a `Signed-off-by` line. The easiest way to do this is with the `-s` flag: + +```bash +git commit -s -m "feat: add awesome feature" +``` + +This appends a line like: + +``` +Signed-off-by: Your Name +``` + +Make sure the name and email match your Git configuration: + +```bash +git config user.name "Your Name" +git config user.email "your.email@example.com" +``` + +### Retroactive Sign-Off (Remediation) + +If you submitted a PR **before** the DCO check was enabled, you do **not** need to rebase. You have two options: + +#### Option 1: Reply to the bot's prompt + +When the DCO check fails, a bot will comment on your PR asking if you'd like to sign. +Simply reply with the following exact phrase: + +> I have read the DCO document and I hereby sign the DCO + +The bot will then push a signed-off remediation commit to your branch automatically. + +#### Option 2: Push a remediation commit manually + +```bash +git commit --allow-empty -s -m "chore: retroactive DCO sign-off" +git push +``` + +A third-party (e.g. a maintainer) can also sign off on your behalf by pushing a remediation commit to your branch. + ## Getting Started 1. **Fork the repository** on GitHub @@ -47,7 +95,7 @@ This project adheres to a [Code of Conduct](CODE_OF_CONDUCT.md). By participatin 1. Look for issues labeled `good first issue` or `help wanted` 2. Comment on the issue to let others know you're working on it 3. Create a feature branch from `main` -4. Make your changes +4. Make your changes and commit with the `-s` flag (see [DCO](#developer-certificate-of-origin-dco)) 5. Submit a pull request ## Development Setup