Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
419f26e
Add optional pre-commit hook for code formatting verification
jamescrosswell May 1, 2026
539134b
Fix pre-commit hook to use dotnet format exit code
jamescrosswell May 3, 2026
9770082
Suppress CA2255 in test projects
jamescrosswell May 3, 2026
aa396a3
Match pre-commit hook to CI formatting behavior
jamescrosswell May 3, 2026
aed5d9a
fix(pre-commit): ensure stashed changes are always restored
jamescrosswell May 3, 2026
1c00577
fix(pre-commit): improve stash conflict guidance
jamescrosswell May 3, 2026
d0f43cf
simplify(pre-commit): remove stash logic
jamescrosswell May 3, 2026
bff2d6c
feat(dev): add setup-hooks command
jamescrosswell May 4, 2026
d3acfd4
remove: scripts/setup-hooks.sh
jamescrosswell May 4, 2026
ff80f08
fix(pre-commit): scope dotnet format to staged files only
jamescrosswell May 4, 2026
348ff62
fix(pre-commit): guard against grep exit code when no C# files staged
jamescrosswell May 4, 2026
cb7245a
feat(dev): add remove-hooks command
jamescrosswell May 4, 2026
17254ac
fix(pre-commit): handle file paths with spaces correctly
jamescrosswell May 4, 2026
38f435d
fix(pre-commit): skip format check when unstaged changes are present
jamescrosswell May 5, 2026
a3f0dd5
docs(contributing): update git hooks setup instructions
jamescrosswell May 6, 2026
d1f2687
fix(pre-commit): show dotnet format output on failure
jamescrosswell May 6, 2026
2db003e
fix(gitattributes): enforce LF line endings for .githooks/
jamescrosswell May 6, 2026
99bae81
Move Git Hooks section below Solution Filters in CONTRIBUTING
jamescrosswell Jul 10, 2026
ee1b527
Remove now-redundant CA2255 SuppressMessage attributes
jamescrosswell Jul 10, 2026
1d08d13
Move test diagnostic suppressions to test/.editorconfig
jamescrosswell Jul 10, 2026
b5e1c98
Quote glob pattern in pre-commit format exclude
jamescrosswell Jul 10, 2026
d604b31
Make dev.cs remove-hooks idempotent
jamescrosswell Jul 10, 2026
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
10 changes: 0 additions & 10 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -208,13 +208,3 @@ indent_style = unset
insert_final_newline = false
tab_width = unset
trim_trailing_whitespace = false

# Disable AOT analyser for test files
[test/**/*.cs]
dotnet_diagnostic.IL2026.severity = none
dotnet_diagnostic.IL2070.severity = none
dotnet_diagnostic.IL2075.severity = none
dotnet_diagnostic.IL2090.severity = none

# This appears to be broken and results in false positives (causing dotnet format to delete valid test scenarios)
dotnet_diagnostic.xUnit1025.severity = none
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
*.sln text=auto eol=crlf
*.slnx text=auto eol=lf
*.sh eol=lf
.githooks/* eol=lf
*.ps1 eol=lf
CHANGELOG.md merge=union

Expand Down
45 changes: 45 additions & 0 deletions .githooks/pre-commit
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
#!/bin/bash
Comment thread
cursor[bot] marked this conversation as resolved.
set -e
Comment thread
cursor[bot] marked this conversation as resolved.

echo "🔍 Checking code formatting..."

if ! git diff --quiet; then
echo "⚠️ Skipping format check: unstaged changes present."
echo " Stage or stash all changes before committing to enable the format check."
exit 0
fi

INCLUDE_ARGS=()
while IFS= read -r f; do
INCLUDE_ARGS+=(--include "$f")
done < <(git diff --cached --name-only --diff-filter=ACM | grep '\.cs$' || true)
Comment thread
jamescrosswell marked this conversation as resolved.
Comment thread
jamescrosswell marked this conversation as resolved.
Comment thread
jamescrosswell marked this conversation as resolved.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

note: --diff-filter=ACM

I wasn't sure if filter --diff-filter=ACM also includes files that are renamed, but also contain changes. I gave it a quick test and it seems that they are. ✅


if [ ${#INCLUDE_ARGS[@]} -eq 0 ]; then
echo "✅ No C# files staged."
exit 0
fi

FORMAT_OUTPUT=$(dotnet format Sentry.slnx --no-restore \
"${INCLUDE_ARGS[@]}" \

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

praise: great optimization ... dotnet format on the "full" solution takes quite a while

--exclude ./modules "./**/*OptionsSetup.cs" ./test/Sentry.Tests/AttributeReaderTests.cs 2>&1) || {
echo ""
echo "❌ dotnet format failed:"
echo "$FORMAT_OUTPUT"
echo ""
exit 1
}

if ! git diff --quiet; then
echo ""
echo "❌ Code formatting issues found!"
echo ""
echo "Please stage the formatting fixes and commit again:"
echo ""
echo " git add -u"
Comment thread
sentry-warden[bot] marked this conversation as resolved.
echo " git commit"
echo ""
exit 1
fi

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Autofix enables skip bypass

High Severity

After dotnet format writes fixes, the working tree is dirty and the hook exits with failure. A follow-up git commit without staging those fixes hits the unstaged-changes guard, exits successfully, and lets the original unformatted staged content through. The formatting changes stay unstaged while the bad formatting is committed, so the hook fails open on the exact retry path it tells developers to use if they omit git add -u.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit b5e1c98. Configure here.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

This is intentional rather than a defect. The unstaged-changes guard exists specifically so dotnet format never touches work-in-progress, and it prints a visible ⚠️ Skipping format check rather than failing silently. This is an opt-in convenience hook — CI remains the actual formatting gate — so on the (documented) retry path the worst case is a formatting-only CI failure, not committing broken code. Enforcing formatting on a dirty tree would require a stash-based partial-format dance that risks mangling WIP, which we deliberately avoid. Leaving as-is.


echo "✅ Code formatting looks good!"
exit 0
25 changes: 25 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,31 @@ These solution filters get generated automatically by `/scripts/generate-solutio

Also note that script generates a `.generated.NoMobile.slnx` solution, which is an identical copy of `Sentry.slnx`. Again, we don't recommend opening this directly. It exists as a round about way to conditionally set build properties based on the solution name in certain solution filters. You should instead use those solution filters (e.g. `SentryNoMobile.slnf`) when working in the Sentry codebase.

## Git Hooks (Optional but Recommended)

To automatically check and fix code formatting before committing, you can set up a pre-commit hook:

```bash
./dev.cs setup-hooks
```

Before each commit, the hook runs `dotnet format` against your staged `.cs` files and auto-fixes any formatting issues. If fixes were applied, the commit is blocked — just stage the fixes and try again:

```bash
git add -u
git commit
```

Note: the hook skips automatically if you have unstaged changes, to avoid touching work in progress.

To opt out at any time:

```bash
./dev.cs remove-hooks
```

**Note:** You can also bypass the hook for a specific commit using `git commit --no-verify` if needed.

## API changes approval process

This repository uses [Verify](https://github.com/VerifyTests/Verify) to store the public API diffs in snapshot files. When a change involves modifying the public API area (by for example adding a public method), that change will need to be approved, otherwise the CI process will fail.
Expand Down
19 changes: 19 additions & 0 deletions dev.cs
Comment thread
jamescrosswell marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,25 @@ public Task<int> AiUpdateAsync(GlobalOptions options = default!)
return RunStepAsync("npx @sentry/dotagents install", "npx", "@sentry/dotagents install", options.DryRun);
}

[Command("setup-hooks", Description = "Configure git to use the repo's pre-commit hooks from .githooks/.")]
public Task<int> SetupHooksAsync(GlobalOptions options = default!)
{
Console.WriteLine("[dev] Configuring git hooks path to .githooks/");
return RunStepAsync("git config core.hooksPath", "git", "config core.hooksPath .githooks", options.DryRun);
Comment thread
jamescrosswell marked this conversation as resolved.
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Setup skips executable bit

Medium Severity

setup-hooks only sets core.hooksPath and never ensures .githooks/pre-commit is executable. On macOS/Linux, Git silently ignores a non-executable hook, so after ./dev.cs setup-hooks commits proceed with no formatting check and only a brief ignored-hook hint. Contributors can believe the optional hook is enabled when it is not running at all.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit b5e1c98. Configure here.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Not applicable here — .githooks/pre-commit is committed with mode 100755 (executable), so git checks it out executable on clone/checkout. setup-hooks therefore doesn't need to chmod it, and on Windows git runs hooks via its bundled shell regardless of the exec bit. No change needed.


[Command("remove-hooks", Description = "Restore default git hooks behaviour (stops using .githooks/).")]
public async Task<int> RemoveHooksAsync(GlobalOptions options = default!)
{
Console.WriteLine("[dev] Restoring default git hooks path");
var exitCode = await RunStepAsync("git config --unset core.hooksPath", "git", "config --unset core.hooksPath", options.DryRun);

// git exits with code 5 when the key doesn't exist. That's already the
// desired end state, so treat it as success to keep remove-hooks idempotent
// (e.g. running it twice, or before setup-hooks).
return exitCode == 5 ? 0 : exitCode;
}
Comment thread
cursor[bot] marked this conversation as resolved.

[Command("nrest", Description = "Restore the default CI solution.")]
public Task<int> SolutionRestoreAsync(
[Argument("solution", Description = "Solution file to restore. Defaults to platform-specific CI solution if omitted.")] string? solution = null,
Expand Down
14 changes: 14 additions & 0 deletions test/.editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Settings that apply only to test code under /test
# (kept here so diagnostic suppressions for tests are easy to find).

[*.cs]

# Disable AOT analyser for test files
dotnet_diagnostic.IL2026.severity = none
dotnet_diagnostic.IL2070.severity = none
dotnet_diagnostic.IL2075.severity = none
dotnet_diagnostic.IL2090.severity = none
dotnet_diagnostic.CA2255.severity = none

# This appears to be broken and results in false positives (causing dotnet format to delete valid test scenarios)
dotnet_diagnostic.xUnit1025.severity = none
1 change: 0 additions & 1 deletion test/CommonModuleInit.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
public static class CommonModuleInit
{
[ModuleInitializer]
[SuppressMessage("Usage", "CA2255:The \'ModuleInitializer\' attribute should not be used in libraries")]
public static void Init()
{
VerifyDiffPlex.Initialize();
Expand Down
1 change: 0 additions & 1 deletion test/Sentry.Tests/ModuleInit.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
public static class ModuleInit
{
[ModuleInitializer]
[SuppressMessage("Usage", "CA2255:The \'ModuleInitializer\' attribute should not be used in libraries")]
public static void Init()
{
CultureInfo.DefaultThreadCurrentUICulture = new CultureInfo("en-US");
Expand Down
Loading