Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
130 changes: 130 additions & 0 deletions .github/scripts/post-precommit-suggestions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
/**
* Posts pre-commit suggestions as GitHub PR comments
* Each suggestion is tracked with a unique marker to avoid duplicates across multiple workflow runs
*/

const crypto = require('crypto'); // used to hash suggestion content

module.exports = async ({ github, context, diff }) => {
// Get all existing comments to check for duplicates
const { data: comments } = await github.rest.issues.listComments({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
});

// Parse the diff into file chunks
const diffLines = diff.split('\n');
let currentFile = null;
let currentHunk = null;
let fileChanges = [];

for (let i = 0; i < diffLines.length; i++) {
const line = diffLines[i];

// New file in diff
if (line.startsWith('diff --git')) {
if (currentFile && currentHunk) {
fileChanges.push({ file: currentFile, hunk: currentHunk });
}
currentFile = null;
currentHunk = null;
} else if (line.startsWith('--- a/')) {
currentFile = line.substring(6);
} else if (line.startsWith('+++ b/')) {
if (!currentFile) {
currentFile = line.substring(6);
}
} else if (line.startsWith('@@')) {
// Save previous hunk if exists
if (currentFile && currentHunk) {
fileChanges.push({ file: currentFile, hunk: currentHunk });
}

// Parse hunk header: @@ -start,count +start,count @@
const match = line.match(/@@ -(\d+),?\d* \+(\d+),?\d* @@/);
if (match) {
currentHunk = {
oldStart: parseInt(match[1]),
newStart: parseInt(match[2]),
oldLines: [],
newLines: [],
contextBefore: [],
contextAfter: []
};
}
} else if (currentHunk) {
// Collect the changes
if (line.startsWith('-')) {
currentHunk.oldLines.push(line.substring(1));
} else if (line.startsWith('+')) {
currentHunk.newLines.push(line.substring(1));
} else if (line.startsWith(' ')) {
// Context line
if (currentHunk.oldLines.length === 0 && currentHunk.newLines.length === 0) {
currentHunk.contextBefore.push(line.substring(1));
} else {
currentHunk.contextAfter.push(line.substring(1));
}
}
}
}

// Save last hunk
if (currentFile && currentHunk) {
fileChanges.push({ file: currentFile, hunk: currentHunk });
}

// Filter out changes that already have suggestions posted
const newChanges = [];
for (const change of fileChanges) {
const { file, hunk } = change;
const suggestionContent = hunk.newLines.join('\n');

// Create a unique marker for this specific suggestion
const hash = crypto.createHash('md5').update(file + suggestionContent).digest('hex').substring(0, 8);
const marker = `<!-- pre-commit-suggestion-${hash} -->`;

// Check if this exact suggestion already exists
const alreadyExists = comments.some(comment => comment.body.includes(marker));

if (!alreadyExists) {
newChanges.push({ ...change, marker });
}
}

// Only post a comment if there are new suggestions
if (newChanges.length > 0) {
let body = "Pre-commit found some formatting changes. You can apply each suggestion directly:\n\n";

for (const change of newChanges) {
const { file, hunk, marker } = change;

// Add the unique marker for this suggestion
body += marker + "\n";
body += `### \`${file}\` (line ${hunk.newStart})\n\n`;

// Create suggestion block
body += "```suggestion\n";
body += hunk.newLines.join('\n');
if (hunk.newLines.length > 0 && !hunk.newLines[hunk.newLines.length - 1].endsWith('\n')) {
body += '\n';
}
body += "```\n\n";
}

body += "---\n\n";
body += "🪄 **Quick fix:** Comment `fix formatting` below and I'll apply these changes automatically!\n\n";
body += "**Other options:**\n";
body += "- Apply the suggestions above directly in GitHub\n";
body += "- Run `pre-commit` locally and commit again\n\n";
body += "Need help? Check the [README](https://github.com/" + context.repo.owner + "/" + context.repo.repo + "/blob/main/README.md).\n";

await github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: body
});
}
};
46 changes: 32 additions & 14 deletions .github/workflows/pre-commit-check.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@
# See instructions in the README.md in the repo root for more details.
name: Run pre-commit to check that formatting is correct
on:
push:
branches: [master, main]
pull_request:

jobs:
Expand All @@ -23,21 +21,41 @@ jobs:
uses: tj-actions/changed-files@24d32ffd492484c1d75e0c0b894501ddb9d30d62 # ratchet:tj-actions/changed-files@v47

- uses: pre-commit/action@2c7b3805fd2a0fd8c1884dcaebf91fc102a13ecd # ratchet:pre-commit/[email protected]
id: pre-commit
continue-on-error: true
with:
extra_args: --files ${{ steps.changed-files.outputs.all_changed_files }}

# Send a notification to the PR if pre-commit fails
- name: Notify on failure
if: failure() && github.event_name == 'pull_request'
# Capture the diff of proposed changes
- name: Get proposed changes
if: steps.pre-commit.outcome == 'failure' && github.event_name == 'pull_request'
id: get-diff
run: |
# Get the diff of changes that pre-commit would make
git diff > proposed-changes.diff

# Check if there are actual changes
if [ -s proposed-changes.diff ]; then
echo "has_changes=true" >> $GITHUB_OUTPUT
# Store diff for use in the next step, escaping for JSON
DIFF_CONTENT=$(cat proposed-changes.diff)
echo "diff_content<<EOF" >> $GITHUB_OUTPUT
echo "$DIFF_CONTENT" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
else
echo "has_changes=false" >> $GITHUB_OUTPUT
fi

# Post proposed changes as review comments with suggestions
- name: Post proposed changes
if: steps.get-diff.outputs.has_changes == 'true' && github.event_name == 'pull_request'
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # ratchet:actions/[email protected]
with:
script: |
let body = "⚠️ **Pre-commit checks failed!** ⚠️\n\n";
body += "Please run `pre-commit install` locally and fix the issues before pushing again.\n\n";
body += "If you need help, refer to the [README](https://github.com/${{ github.repository }}/blob/main/README.md) in the repo root.\n";
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: body
})
const script = require('./.github/scripts/post-precommit-suggestions.js');
await script({ github, context, diff: `${{ steps.get-diff.outputs.diff_content }}` });

# Fail the workflow if pre-commit failed
- name: Fail if pre-commit failed
if: steps.pre-commit.outcome == 'failure'
run: exit 1
51 changes: 51 additions & 0 deletions .github/workflows/pre-commit-fix.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
name: Run pre-commit when requested via comment
on:
issue_comment:
types: [created]

jobs:
pre_commit_fix:
runs-on: ubuntu-latest
# Only run if comment is on a PR with the main repo, and if it contains the magic keywords
if: >
github.event.issue.pull_request &&
startsWith(github.event.comment.body, 'fix formatting')

steps:
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # ratchet:actions/[email protected]

# Action runs on the issue comment, so we don't get the PR by default.
# Use the GitHub CLI to check out the PR.
- name: Checkout Pull Request
run: gh pr checkout ${{ github.event.issue.number }}

- uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # ratchet:actions/[email protected]
with:
python-version: "3.14"
cache: "pip"

# Only run it on changed files with: https://github.com/pre-commit/action/issues/7
- id: changed-files
uses: tj-actions/changed-files@24d32ffd492484c1d75e0c0b894501ddb9d30d62 # ratchet:tj-actions/changed-files@v47

- uses: pre-commit/action@2c7b3805fd2a0fd8c1884dcaebf91fc102a13ecd # ratchet:pre-commit/[email protected]
id: pre-commit
continue-on-error: true
with:
extra_args: --files ${{ steps.changed-files.outputs.all_changed_files }}

- name: Check if any files changed
run: |
git diff --exit-code || echo "changed=YES" >> $GITHUB_ENV
echo "Files changed: ${{ env.changed }}"

- name: Commit and push changes
if: env.changed == 'YES'
run: |
git config user.name 'Seqera Docs Bot'
git config user.email '[email protected]'
git config push.default upstream
git add .
git status
git commit -m "[automated] Fix code formatting"
git push