Skip to content
Draft
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
27 changes: 27 additions & 0 deletions .github/workflows/examples/auto-fix.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
name: cpp-linter (auto-fix)
on:
pull_request:
branches: [main, master, develop]
paths: ['**.c', '**.cpp', '**.h', '**.hpp', '**.cxx', '**.hxx', '**.cc', '**.hh', '**CMakeLists.txt', 'meson.build', '**.cmake']

jobs:
cpp-linter:
runs-on: ubuntu-latest
permissions:
contents: write # needed for auto-fix commits
pull-requests: write
steps:
- uses: actions/checkout@v5

- uses: cpp-linter/cpp-linter-action@v2
id: linter
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
style: 'file' # Use .clang-format config file
tidy-checks: '-*' # disable clang-tidy
auto-fix: 'true' # auto-apply clang-format fixes

- name: Fail fast?!
if: steps.linter.outputs.clang-format-checks-failed > 0
run: exit 1
31 changes: 31 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,37 @@ For all explanations of our available input parameters and output variables, see

See also our [example recipes][recipes-doc].

### Auto-fix clang-format issues

You can enable automatic fixing of clang-format issues by setting `auto-fix: 'true'`.
When enabled, the action will:

1. Run clang-format detection as usual
2. Apply `clang-format -i` to fix any files with style issues
3. Commit and push the formatted changes back to the PR branch

```yaml
steps:
- uses: actions/checkout@v5
- uses: cpp-linter/cpp-linter-action@v2
id: linter
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
style: 'file'
auto-fix: 'true' # automatically fix format issues
```

> [!TIP]
> To prevent the auto-fix commit from triggering another CI run, include a
> `[skip ci]` (or `[ci skip]`, `[no ci]`, etc.) tag in your custom commit message:
>
> ```yaml
> with:
> auto-fix: 'true'
> auto-fix-commit-msg: 'style: apply styling format fix [skip ci]'
> ```

## Used By

<p align="center">
Expand Down
46 changes: 46 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,24 @@ inputs:
Set this option to `true` to prevent Pull Request reviews from approving or requesting changes.
default: 'false'
required: false
auto-fix:
description: |
Set this option to `true` to automatically apply clang-format fixes
and commit them back to the PR branch.

When enabled, cpp-linter runs with ``--fix``, which applies
``clang-format -i`` on files with style issues. After that,
a new commit is pushed to the PR branch with the formatted changes.

This option has no effect on clang-tidy issues.
default: 'false'
required: false
auto-fix-commit-msg:
description: |
Custom commit message for the auto-fix commit.
Only used when ``auto-fix`` is ``true``.
default: 'style: apply styling format fix'
required: false
jobs:
description: |
The number of jobs to run in parallel.
Expand Down Expand Up @@ -458,6 +476,9 @@ runs:
'--passive-reviews=${{ inputs.passive-reviews }}'
'--jobs=${{ inputs.jobs }}'
]
if '${{ inputs.auto-fix }}' == 'true' {
$args = ($args | append ['--fix'])

@2bndy5 2bndy5 Jun 9, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

The self-test CI fails here because all variables are immutable by default in nushell. To declare them mutable, use mut instead of let (above this line):

-       let args = [
+       mut args = [

}
mut uv_args = [run --no-sync --project $action_path --directory (pwd)]

let gh_action_debug = $env | get --optional 'ACTIONS_STEP_DEBUG'
Expand All @@ -483,3 +504,28 @@ runs:

print $"\n(ansi purple)Running cpp-linter(ansi reset)"
^$'($env.UV_INSTALL_DIR)/uv' ...$uv_args cpp-linter ...$args

- name: Auto-commit clang-format fixes
if: inputs.auto-fix == 'true' || inputs.auto-fix == true
shell: nu {0}
run: |
let has_changes = (^git diff --exit-code) | complete | $in.exit_code != 0

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

If this returns true, then it is because cpp-linter left some cache artifacts behind. Currently, cpp-linter makes sure the scanned source files are unchanged. Any changes are done in memory or in cache. If changes are done to the C/C++ sources, then they are undone before exiting run_clang_format() or run_clang_tidy().

This feature needs special handling in cpp-linter first; see cpp-linter/cpp-linter#82 for a discussion of the complexity that hasn't been addressed yet.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Oh sorry, I didn't see you submitted cpp-linter/cpp-linter#202

if $has_changes {
^git config user.name 'github-actions[bot]'
^git config user.email '41898282+github-actions[bot]@users.noreply.github.com'
Comment on lines +514 to +515

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Better to use the env vars present: GITHUB_ACTOR and GITHUB_ACTOR_ID. This way, the commits will be associated with the user that triggered the event for the workflow's run.

Suggested change
^git config user.name 'github-actions[bot]'
^git config user.email '41898282+github-actions[bot]@users.noreply.github.com'
^git config user.name $"($env.GITHUB_ACTOR)"
^git config user.email $"($env.GITHUB_ACTOR_ID)+($env.GITHUB_ACTOR)@users.noreply.github.com"

Also, this can be done for a single git commit command instead of all git commands in the CI workflow:

let git_user = $"user.name=($env.GITHUB_ACTOR)"
let git_email = $"user.email=($env.GITHUB_ACTOR_ID)+($env.GITHUB_ACTOR)@users.noreply.github.com"

^git -c $git_user -c $git_email commit -m $commit_msg

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I am not sure if the outside contributor can commit with their own user/email in GitHub actions.
If it's not a problem. It seems good. othersie use a bot user like github-actions[bot] dependabot[bot], pre-commit-ci[bot] are also not bad.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

github-actions[bot] would be good enough if the GITHUB_TOKEN env var is never supplied a PAT (personal access token). If using a PAT, then the owner of the PAT will not be the github-actions[bot].

By using the GITHUB_ACTOR(_ID), we can at least point to the committer that caused violations to be auto-fixed with a new commit.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I am not sure if the outside contributor can commit with their own user/email in GitHub actions

I do this all the time with my automated publishing scripts. See the nu script I wrote for publishing packages from cpp-linter-rs

^git add -A
let commit_msg = if ('${{ inputs.auto-fix-commit-msg }}' | is-empty) {
'style: apply styling format fix'
} else {
'${{ inputs.auto-fix-commit-msg }}'
}
^git commit -m $"($commit_msg)"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

See my previous comment about setting the user name and email of a single git command.

let push_result = (^git push) | complete
if $push_result.exit_code != 0 {
print $"(ansi yellow)::warning title=Auto-fix push failed::($push_result.stderr)(ansi reset)"
} else {
print $"(ansi green)Auto-fix commit pushed successfully(ansi reset)"
}
} else {
print $"(ansi green)No formatting changes to commit(ansi reset)"
}
5 changes: 5 additions & 0 deletions docs/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,11 @@ inputs:
passive-reviews:
minimum-version: '2.12.0'
required-permission: 'pull-requests: write #pull-request-reviews'
auto-fix:
minimum-version: '2.19.0'
required-permission: 'contents: write #auto-fix'
auto-fix-commit-msg:
minimum-version: '2.19.0'
jobs:
minimum-version: '2.11.0'
cache-enable:
Expand Down
11 changes: 11 additions & 0 deletions docs/examples/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,22 @@
[style]: ../inputs-outputs.md#style
[tidy-checks]: ../inputs-outputs.md#tidy-checks
[thread-comments]: ../inputs-outputs.md#thread-comments
[auto-fix]: ../inputs-outputs.md#auto-fix

# Recipes

Here are some example workflows to get started quickly.

=== "auto-fix clang-format"

``` yaml
--8<-- ".github/workflows/examples/auto-fix.yml"
```

1. See also [`auto-fix`][auto-fix]
2. See also [`style`][style]
3. See also [`tidy-checks`][tidy-checks]

=== "only clang-tidy"

``` yaml
Expand Down
12 changes: 12 additions & 0 deletions docs/permissions.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,3 +78,15 @@ The [`tidy-review`](inputs-outputs.md#tidy-review), [`format-review`](inputs-out
permissions:
pull-requests: write
```

## Auto-fix

The [`auto-fix`](inputs-outputs.md#auto-fix) feature requires the following permission
in addition to any other permissions needed for other features:

```yaml
permissions:
contents: write # (1)!
```

1. Needed to commit and push the formatted changes back to the PR branch.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

We should also note that this permission is only needed by the token used in actions/checkout (which defaults to github.token).

However, the github.token is NOT able to trigger new CI runs when used to push a commit. If users want the auto-fix commit to trigger a new CI run, then they need to provide it with a PAT that has contents: write permission.

- uses: actions/checkout@v6
  with:
    # MY_TOKEN must have `contents: write` permission.
    # using default token does not trigger CI runs on `git push`
    token: ${{ secrets.MY_TOKEN }}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Another problem that might arise here is when auto-fixing PR changes from a third-party fork. My previous comment was made with experience about pushing changes to the same remote. If the changes need to be pushed to a different remote, then that is a different set of permissions.

Loading