Bumps [actions/checkout](https://github.com/actions/checkout) from 3 to 4.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a href="https://github.com/actions/checkout/releases">actions/checkout's releases</a>.</em></p>
<blockquote>
<h2>v4.0.0</h2>
<h2>What's Changed</h2>
<ul>
<li>Update default runtime to node20 by <a href="https://github.com/takost"><code>@​takost</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/1436">actions/checkout#1436</a></li>
<li>Support fetching without the --progress option by <a href="https://github.com/simonbaird"><code>@​simonbaird</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/1067">actions/checkout#1067</a></li>
<li>Release 4.0.0 by <a href="https://github.com/takost"><code>@​takost</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/1447">actions/checkout#1447</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/takost"><code>@​takost</code></a> made their first contribution in <a href="https://redirect.github.com/actions/checkout/pull/1436">actions/checkout#1436</a></li>
<li><a href="https://github.com/simonbaird"><code>@​simonbaird</code></a> made their first contribution in <a href="https://redirect.github.com/actions/checkout/pull/1067">actions/checkout#1067</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a href="https://github.com/actions/checkout/compare/v3...v4.0.0">https://github.com/actions/checkout/compare/v3...v4.0.0</a></p>
<h2>v3.6.0</h2>
<h2>What's Changed</h2>
<ul>
<li>Mark test scripts with Bash'isms to be run via Bash by <a href="https://github.com/dscho"><code>@​dscho</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/1377">actions/checkout#1377</a></li>
<li>Add option to fetch tags even if fetch-depth &gt; 0 by <a href="https://github.com/RobertWieczoreck"><code>@​RobertWieczoreck</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/579">actions/checkout#579</a></li>
<li>Release 3.6.0 by <a href="https://github.com/luketomlinson"><code>@​luketomlinson</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/1437">actions/checkout#1437</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/RobertWieczoreck"><code>@​RobertWieczoreck</code></a> made their first contribution in <a href="https://redirect.github.com/actions/checkout/pull/579">actions/checkout#579</a></li>
<li><a href="https://github.com/luketomlinson"><code>@​luketomlinson</code></a> made their first contribution in <a href="https://redirect.github.com/actions/checkout/pull/1437">actions/checkout#1437</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a href="https://github.com/actions/checkout/compare/v3.5.3...v3.6.0">https://github.com/actions/checkout/compare/v3.5.3...v3.6.0</a></p>
<h2>v3.5.3</h2>
<h2>What's Changed</h2>
<ul>
<li>Fix: Checkout Issue in self hosted runner due to faulty submodule check-ins by <a href="https://github.com/megamanics"><code>@​megamanics</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/1196">actions/checkout#1196</a></li>
<li>Fix typos found by codespell by <a href="https://github.com/DimitriPapadopoulos"><code>@​DimitriPapadopoulos</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/1287">actions/checkout#1287</a></li>
<li>Add support for sparse checkouts by <a href="https://github.com/dscho"><code>@​dscho</code></a> and <a href="https://github.com/dfdez"><code>@​dfdez</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/1369">actions/checkout#1369</a></li>
<li>Release v3.5.3 by <a href="https://github.com/TingluoHuang"><code>@​TingluoHuang</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/1376">actions/checkout#1376</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/megamanics"><code>@​megamanics</code></a> made their first contribution in <a href="https://redirect.github.com/actions/checkout/pull/1196">actions/checkout#1196</a></li>
<li><a href="https://github.com/DimitriPapadopoulos"><code>@​DimitriPapadopoulos</code></a> made their first contribution in <a href="https://redirect.github.com/actions/checkout/pull/1287">actions/checkout#1287</a></li>
<li><a href="https://github.com/dfdez"><code>@​dfdez</code></a> made their first contribution in <a href="https://redirect.github.com/actions/checkout/pull/1369">actions/checkout#1369</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a href="https://github.com/actions/checkout/compare/v3...v3.5.3">https://github.com/actions/checkout/compare/v3...v3.5.3</a></p>
<h2>v3.5.2</h2>
<h2>What's Changed</h2>
<ul>
<li>Fix: Use correct API url / endpoint in GHES by <a href="https://github.com/fhammerl"><code>@​fhammerl</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/1289">actions/checkout#1289</a> based on <a href="https://redirect.github.com/actions/checkout/issues/1286">#1286</a> by <a href="https://github.com/1newsr"><code>@​1newsr</code></a></li>
</ul>
<p><strong>Full Changelog</strong>: <a href="https://github.com/actions/checkout/compare/v3.5.1...v3.5.2">https://github.com/actions/checkout/compare/v3.5.1...v3.5.2</a></p>
<h2>v3.5.1</h2>
<h2>What's Changed</h2>
<ul>
<li>Improve checkout performance on Windows runners by upgrading <code>@​actions/github</code> dependency by <a href="https://github.com/BrettDong"><code>@​BrettDong</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/1246">actions/checkout#1246</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/BrettDong"><code>@​BrettDong</code></a> made their first contribution in <a href="https://redirect.github.com/actions/checkout/pull/1246">actions/checkout#1246</a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a href="https://github.com/actions/checkout/blob/main/CHANGELOG.md">actions/checkout's changelog</a>.</em></p>
<blockquote>
<h1>Changelog</h1>
<h2>v4.1.0</h2>
<ul>
<li><a href="https://redirect.github.com/actions/checkout/pull/1396">Add support for partial checkout filters</a></li>
</ul>
<h2>v4.0.0</h2>
<ul>
<li><a href="https://redirect.github.com/actions/checkout/pull/1067">Support fetching without the --progress option</a></li>
<li><a href="https://redirect.github.com/actions/checkout/pull/1436">Update to node20</a></li>
</ul>
<h2>v3.6.0</h2>
<ul>
<li><a href="https://redirect.github.com/actions/checkout/pull/1377">Fix: Mark test scripts with Bash'isms to be run via Bash</a></li>
<li><a href="https://redirect.github.com/actions/checkout/pull/579">Add option to fetch tags even if fetch-depth &gt; 0</a></li>
</ul>
<h2>v3.5.3</h2>
<ul>
<li><a href="https://redirect.github.com/actions/checkout/pull/1196">Fix: Checkout fail in self-hosted runners when faulty submodule are checked-in</a></li>
<li><a href="https://redirect.github.com/actions/checkout/pull/1287">Fix typos found by codespell</a></li>
<li><a href="https://redirect.github.com/actions/checkout/pull/1369">Add support for sparse checkouts</a></li>
</ul>
<h2>v3.5.2</h2>
<ul>
<li><a href="https://redirect.github.com/actions/checkout/pull/1289">Fix api endpoint for GHES</a></li>
</ul>
<h2>v3.5.1</h2>
<ul>
<li><a href="https://redirect.github.com/actions/checkout/pull/1246">Fix slow checkout on Windows</a></li>
</ul>
<h2>v3.5.0</h2>
<ul>
<li><a href="https://redirect.github.com/actions/checkout/pull/1237">Add new public key for known_hosts</a></li>
</ul>
<h2>v3.4.0</h2>
<ul>
<li><a href="https://redirect.github.com/actions/checkout/pull/1209">Upgrade codeql actions to v2</a></li>
<li><a href="https://redirect.github.com/actions/checkout/pull/1210">Upgrade dependencies</a></li>
<li><a href="https://redirect.github.com/actions/checkout/pull/1225">Upgrade <code>@​actions/io</code></a></li>
</ul>
<h2>v3.3.0</h2>
<ul>
<li><a href="https://redirect.github.com/actions/checkout/pull/1045">Implement branch list using callbacks from exec function</a></li>
<li><a href="https://redirect.github.com/actions/checkout/pull/1050">Add in explicit reference to private checkout options</a></li>
<li>[Fix comment typos (that got added in <a href="https://redirect.github.com/actions/checkout/issues/770">#770</a>)](<a href="https://redirect.github.com/actions/checkout/pull/1057">actions/checkout#1057</a>)</li>
</ul>
<h2>v3.2.0</h2>
<ul>
<li><a href="https://redirect.github.com/actions/checkout/pull/942">Add GitHub Action to perform release</a></li>
<li><a href="https://redirect.github.com/actions/checkout/pull/967">Fix status badge</a></li>
<li><a href="https://redirect.github.com/actions/checkout/pull/1002">Replace datadog/squid with ubuntu/squid Docker image</a></li>
<li><a href="https://redirect.github.com/actions/checkout/pull/964">Wrap pipeline commands for submoduleForeach in quotes</a></li>
<li><a href="https://redirect.github.com/actions/checkout/pull/1029">Update <code>@​actions/io</code> to 1.1.2</a></li>
<li><a href="https://redirect.github.com/actions/checkout/pull/1039">Upgrading version to 3.2.0</a></li>
</ul>
<h2>v3.1.0</h2>
<ul>
<li><a href="https://redirect.github.com/actions/checkout/pull/939">Use <code>@​actions/core</code> <code>saveState</code> and <code>getState</code></a></li>
<li><a href="https://redirect.github.com/actions/checkout/pull/922">Add <code>github-server-url</code> input</a></li>
</ul>
<h2>v3.0.2</h2>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a href="https://github.com/actions/checkout/commit/8ade135a41bc03ea155e62e844d188df1ea18608"><code>8ade135</code></a> Prepare 4.1.0 release (<a href="https://redirect.github.com/actions/checkout/issues/1496">#1496</a>)</li>
<li><a href="https://github.com/actions/checkout/commit/c533a0a4cfc4962971818edcfac47a2899e69799"><code>c533a0a</code></a> Add support for partial checkout filters (<a href="https://redirect.github.com/actions/checkout/issues/1396">#1396</a>)</li>
<li><a href="https://github.com/actions/checkout/commit/72f2cec99f417b1a1c5e2e88945068983b7965f9"><code>72f2cec</code></a> Update README.md for V4 (<a href="https://redirect.github.com/actions/checkout/issues/1452">#1452</a>)</li>
<li><a href="https://github.com/actions/checkout/commit/3df4ab11eba7bda6032a0b82a6bb43b11571feac"><code>3df4ab1</code></a> Release 4.0.0 (<a href="https://redirect.github.com/actions/checkout/issues/1447">#1447</a>)</li>
<li><a href="https://github.com/actions/checkout/commit/8b5e8b768746b50394015010d25e690bfab9dfbc"><code>8b5e8b7</code></a> Support fetching without the --progress option (<a href="https://redirect.github.com/actions/checkout/issues/1067">#1067</a>)</li>
<li><a href="https://github.com/actions/checkout/commit/97a652b80035363df47baee5031ec8670b8878ac"><code>97a652b</code></a> Update default runtime to node20 (<a href="https://redirect.github.com/actions/checkout/issues/1436">#1436</a>)</li>
<li>See full diff in <a href="https://github.com/actions/checkout/compare/v3...v4">compare view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=actions/checkout&package-manager=github_actions&previous-version=3&new-version=4)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)


</details>
Bump actions/checkout from 3 to 4. Bumps [actions/checkout](https://github.com/actions/checkout) from 3 to 4.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a href="https://github.com/actions/checkout/releases">actions/checkout's releases</a>.</em></p>
<blockquote>
<h2>v4.0.0</h2>
<h2>What's Changed</h2>
<ul>
<li>Update default runtime to node20 by <a href="https://github.com/takost"><code>@​takost</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/1436">actions/checkout#1436</a></li>
<li>Support fetching without the --progress option by <a href="https://github.com/simonbaird"><code>@​simonbaird</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/1067">actions/checkout#1067</a></li>
<li>Release 4.0.0 by <a href="https://github.com/takost"><code>@​takost</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/1447">actions/checkout#1447</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/takost"><code>@​takost</code></a> made their first contribution in <a href="https://redirect.github.com/actions/checkout/pull/1436">actions/checkout#1436</a></li>
<li><a href="https://github.com/simonbaird"><code>@​simonbaird</code></a> made their first contribution in <a href="https://redirect.github.com/actions/checkout/pull/1067">actions/checkout#1067</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a href="https://github.com/actions/checkout/compare/v3...v4.0.0">https://github.com/actions/checkout/compare/v3...v4.0.0</a></p>
<h2>v3.6.0</h2>
<h2>What's Changed</h2>
<ul>
<li>Mark test scripts with Bash'isms to be run via Bash by <a href="https://github.com/dscho"><code>@​dscho</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/1377">actions/checkout#1377</a></li>
<li>Add option to fetch tags even if fetch-depth &gt; 0 by <a href="https://github.com/RobertWieczoreck"><code>@​RobertWieczoreck</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/579">actions/checkout#579</a></li>
<li>Release 3.6.0 by <a href="https://github.com/luketomlinson"><code>@​luketomlinson</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/1437">actions/checkout#1437</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/RobertWieczoreck"><code>@​RobertWieczoreck</code></a> made their first contribution in <a href="https://redirect.github.com/actions/checkout/pull/579">actions/checkout#579</a></li>
<li><a href="https://github.com/luketomlinson"><code>@​luketomlinson</code></a> made their first contribution in <a href="https://redirect.github.com/actions/checkout/pull/1437">actions/checkout#1437</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a href="https://github.com/actions/checkout/compare/v3.5.3...v3.6.0">https://github.com/actions/checkout/compare/v3.5.3...v3.6.0</a></p>
<h2>v3.5.3</h2>
<h2>What's Changed</h2>
<ul>
<li>Fix: Checkout Issue in self hosted runner due to faulty submodule check-ins by <a href="https://github.com/megamanics"><code>@​megamanics</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/1196">actions/checkout#1196</a></li>
<li>Fix typos found by codespell by <a href="https://github.com/DimitriPapadopoulos"><code>@​DimitriPapadopoulos</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/1287">actions/checkout#1287</a></li>
<li>Add support for sparse checkouts by <a href="https://github.com/dscho"><code>@​dscho</code></a> and <a href="https://github.com/dfdez"><code>@​dfdez</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/1369">actions/checkout#1369</a></li>
<li>Release v3.5.3 by <a href="https://github.com/TingluoHuang"><code>@​TingluoHuang</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/1376">actions/checkout#1376</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/megamanics"><code>@​megamanics</code></a> made their first contribution in <a href="https://redirect.github.com/actions/checkout/pull/1196">actions/checkout#1196</a></li>
<li><a href="https://github.com/DimitriPapadopoulos"><code>@​DimitriPapadopoulos</code></a> made their first contribution in <a href="https://redirect.github.com/actions/checkout/pull/1287">actions/checkout#1287</a></li>
<li><a href="https://github.com/dfdez"><code>@​dfdez</code></a> made their first contribution in <a href="https://redirect.github.com/actions/checkout/pull/1369">actions/checkout#1369</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a href="https://github.com/actions/checkout/compare/v3...v3.5.3">https://github.com/actions/checkout/compare/v3...v3.5.3</a></p>
<h2>v3.5.2</h2>
<h2>What's Changed</h2>
<ul>
<li>Fix: Use correct API url / endpoint in GHES by <a href="https://github.com/fhammerl"><code>@​fhammerl</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/1289">actions/checkout#1289</a> based on <a href="https://redirect.github.com/actions/checkout/issues/1286">#1286</a> by <a href="https://github.com/1newsr"><code>@​1newsr</code></a></li>
</ul>
<p><strong>Full Changelog</strong>: <a href="https://github.com/actions/checkout/compare/v3.5.1...v3.5.2">https://github.com/actions/checkout/compare/v3.5.1...v3.5.2</a></p>
<h2>v3.5.1</h2>
<h2>What's Changed</h2>
<ul>
<li>Improve checkout performance on Windows runners by upgrading <code>@​actions/github</code> dependency by <a href="https://github.com/BrettDong"><code>@​BrettDong</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/1246">actions/checkout#1246</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/BrettDong"><code>@​BrettDong</code></a> made their first contribution in <a href="https://redirect.github.com/actions/checkout/pull/1246">actions/checkout#1246</a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a href="https://github.com/actions/checkout/blob/main/CHANGELOG.md">actions/checkout's changelog</a>.</em></p>
<blockquote>
<h1>Changelog</h1>
<h2>v4.1.0</h2>
<ul>
<li><a href="https://redirect.github.com/actions/checkout/pull/1396">Add support for partial checkout filters</a></li>
</ul>
<h2>v4.0.0</h2>
<ul>
<li><a href="https://redirect.github.com/actions/checkout/pull/1067">Support fetching without the --progress option</a></li>
<li><a href="https://redirect.github.com/actions/checkout/pull/1436">Update to node20</a></li>
</ul>
<h2>v3.6.0</h2>
<ul>
<li><a href="https://redirect.github.com/actions/checkout/pull/1377">Fix: Mark test scripts with Bash'isms to be run via Bash</a></li>
<li><a href="https://redirect.github.com/actions/checkout/pull/579">Add option to fetch tags even if fetch-depth &gt; 0</a></li>
</ul>
<h2>v3.5.3</h2>
<ul>
<li><a href="https://redirect.github.com/actions/checkout/pull/1196">Fix: Checkout fail in self-hosted runners when faulty submodule are checked-in</a></li>
<li><a href="https://redirect.github.com/actions/checkout/pull/1287">Fix typos found by codespell</a></li>
<li><a href="https://redirect.github.com/actions/checkout/pull/1369">Add support for sparse checkouts</a></li>
</ul>
<h2>v3.5.2</h2>
<ul>
<li><a href="https://redirect.github.com/actions/checkout/pull/1289">Fix api endpoint for GHES</a></li>
</ul>
<h2>v3.5.1</h2>
<ul>
<li><a href="https://redirect.github.com/actions/checkout/pull/1246">Fix slow checkout on Windows</a></li>
</ul>
<h2>v3.5.0</h2>
<ul>
<li><a href="https://redirect.github.com/actions/checkout/pull/1237">Add new public key for known_hosts</a></li>
</ul>
<h2>v3.4.0</h2>
<ul>
<li><a href="https://redirect.github.com/actions/checkout/pull/1209">Upgrade codeql actions to v2</a></li>
<li><a href="https://redirect.github.com/actions/checkout/pull/1210">Upgrade dependencies</a></li>
<li><a href="https://redirect.github.com/actions/checkout/pull/1225">Upgrade <code>@​actions/io</code></a></li>
</ul>
<h2>v3.3.0</h2>
<ul>
<li><a href="https://redirect.github.com/actions/checkout/pull/1045">Implement branch list using callbacks from exec function</a></li>
<li><a href="https://redirect.github.com/actions/checkout/pull/1050">Add in explicit reference to private checkout options</a></li>
<li>[Fix comment typos (that got added in <a href="https://redirect.github.com/actions/checkout/issues/770">#770</a>)](<a href="https://redirect.github.com/actions/checkout/pull/1057">actions/checkout#1057</a>)</li>
</ul>
<h2>v3.2.0</h2>
<ul>
<li><a href="https://redirect.github.com/actions/checkout/pull/942">Add GitHub Action to perform release</a></li>
<li><a href="https://redirect.github.com/actions/checkout/pull/967">Fix status badge</a></li>
<li><a href="https://redirect.github.com/actions/checkout/pull/1002">Replace datadog/squid with ubuntu/squid Docker image</a></li>
<li><a href="https://redirect.github.com/actions/checkout/pull/964">Wrap pipeline commands for submoduleForeach in quotes</a></li>
<li><a href="https://redirect.github.com/actions/checkout/pull/1029">Update <code>@​actions/io</code> to 1.1.2</a></li>
<li><a href="https://redirect.github.com/actions/checkout/pull/1039">Upgrading version to 3.2.0</a></li>
</ul>
<h2>v3.1.0</h2>
<ul>
<li><a href="https://redirect.github.com/actions/checkout/pull/939">Use <code>@​actions/core</code> <code>saveState</code> and <code>getState</code></a></li>
<li><a href="https://redirect.github.com/actions/checkout/pull/922">Add <code>github-server-url</code> input</a></li>
</ul>
<h2>v3.0.2</h2>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a href="https://github.com/actions/checkout/commit/8ade135a41bc03ea155e62e844d188df1ea18608"><code>8ade135</code></a> Prepare 4.1.0 release (<a href="https://redirect.github.com/actions/checkout/issues/1496">#1496</a>)</li>
<li><a href="https://github.com/actions/checkout/commit/c533a0a4cfc4962971818edcfac47a2899e69799"><code>c533a0a</code></a> Add support for partial checkout filters (<a href="https://redirect.github.com/actions/checkout/issues/1396">#1396</a>)</li>
<li><a href="https://github.com/actions/checkout/commit/72f2cec99f417b1a1c5e2e88945068983b7965f9"><code>72f2cec</code></a> Update README.md for V4 (<a href="https://redirect.github.com/actions/checkout/issues/1452">#1452</a>)</li>
<li><a href="https://github.com/actions/checkout/commit/3df4ab11eba7bda6032a0b82a6bb43b11571feac"><code>3df4ab1</code></a> Release 4.0.0 (<a href="https://redirect.github.com/actions/checkout/issues/1447">#1447</a>)</li>
<li><a href="https://github.com/actions/checkout/commit/8b5e8b768746b50394015010d25e690bfab9dfbc"><code>8b5e8b7</code></a> Support fetching without the --progress option (<a href="https://redirect.github.com/actions/checkout/issues/1067">#1067</a>)</li>
<li><a href="https://github.com/actions/checkout/commit/97a652b80035363df47baee5031ec8670b8878ac"><code>97a652b</code></a> Update default runtime to node20 (<a href="https://redirect.github.com/actions/checkout/issues/1436">#1436</a>)</li>
<li>See full diff in <a href="https://github.com/actions/checkout/compare/v3...v4">compare view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=actions/checkout&package-manager=github_actions&previous-version=3&new-version=4)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)


</details>
import dataclasses
import importlib
import os
import re
import sys
from bisect import insort
from pathlib import Path
from typing import Any, List, Optional, Tuple, Union

import pytest

try:
    from mypy import api as mypy_api
    from mypy.version import __version__ as mypy_version

    from pydantic.version import parse_mypy_version

except ImportError:
    mypy_api = None
    mypy_version = None

    parse_mypy_version = lambda _: (0,)  # noqa: E731


MYPY_VERSION_TUPLE = parse_mypy_version(mypy_version)
PYDANTIC_ROOT = Path(__file__).parent.parent.parent

pytestmark = pytest.mark.skipif(
    '--test-mypy' not in sys.argv
    and os.environ.get('PYCHARM_HOSTED') != '1',  # never skip when running via the PyCharm runner
    reason='Test only with "--test-mypy" flag',
)

# This ensures mypy can find the test files, no matter where tests are run from:
os.chdir(Path(__file__).parent.parent.parent)


@dataclasses.dataclass
class MypyCasesBuilder:
    configs: Union[str, List[str]]
    modules: Union[str, List[str]]
    marks: Any = None

    def build(self) -> List[Union[Tuple[str, str], Any]]:
        """
        Produces the cartesian product of the configs and modules, optionally with marks.
        """
        if isinstance(self.configs, str):
            self.configs = [self.configs]
        if isinstance(self.modules, str):
            self.modules = [self.modules]
        built_cases = []
        for config in self.configs:
            for module in self.modules:
                built_cases.append((config, module))
        if self.marks is not None:
            built_cases = [pytest.param(config, module, marks=self.marks) for config, module in built_cases]
        return built_cases


cases = (
    # No plugin
    MypyCasesBuilder(
        ['mypy-default.ini', 'pyproject-default.toml'],
        ['fail1.py', 'fail2.py', 'fail3.py', 'fail4.py', 'pydantic_settings.py'],
    ).build()
    + MypyCasesBuilder(
        ['mypy-default.ini', 'pyproject-default.toml'],
        'success.py',
        pytest.mark.skipif(MYPY_VERSION_TUPLE > (1, 0, 1), reason='Need to handle some more things for mypy >=1.1.1'),
    ).build()
    + MypyCasesBuilder(
        'mypy-default.ini', ['plugin_success.py', 'plugin_success_baseConfig.py', 'metaclass_args.py']
    ).build()
    # Default plugin config
    + MypyCasesBuilder(
        ['mypy-plugin.ini', 'pyproject-plugin.toml'],
        [
            'plugin_success.py',
            'plugin_fail.py',
            'plugin_success_baseConfig.py',
            'plugin_fail_baseConfig.py',
            'pydantic_settings.py',
        ],
    ).build()
    # Strict plugin config
    + MypyCasesBuilder(
        ['mypy-plugin-strict.ini', 'pyproject-plugin-strict.toml'],
        [
            'plugin_success.py',
            'plugin_fail.py',
            'fail_defaults.py',
            'plugin_success_baseConfig.py',
            'plugin_fail_baseConfig.py',
        ],
    ).build()
    # One-off cases
    + [
        ('mypy-plugin.ini', 'custom_constructor.py'),
        ('mypy-plugin.ini', 'generics.py'),
        ('mypy-plugin.ini', 'root_models.py'),
        ('mypy-plugin-strict.ini', 'plugin_default_factory.py'),
        ('mypy-plugin-strict-no-any.ini', 'dataclass_no_any.py'),
        ('mypy-plugin-very-strict.ini', 'metaclass_args.py'),
        pytest.param(
            'pyproject-default.toml',
            'computed_fields.py',
            marks=pytest.mark.skipif(
                sys.version_info < (3, 8),
                reason='cached_property is only available in Python 3.8+, errors are different with mypy 0.971',
            ),
        ),
    ]
)


@dataclasses.dataclass
class MypyTestTarget:
    parsed_mypy_version: Tuple[int, ...]
    output_path: Path


@dataclasses.dataclass
class MypyTestConfig:
    existing: Optional[MypyTestTarget]  # the oldest target with an output that is no older than the installed mypy
    current: MypyTestTarget  # the target for the current installed mypy


def get_test_config(module_path: Path, config_path: Path) -> MypyTestConfig:
    outputs_dir = PYDANTIC_ROOT / 'tests/mypy/outputs'
    outputs_dir.mkdir(exist_ok=True)
    existing_versions = [
        x.name for x in outputs_dir.iterdir() if x.is_dir() and re.match(r'[0-9]+(?:\.[0-9]+)*', x.name)
    ]

    def _convert_to_output_path(v: str) -> Path:
        return outputs_dir / v / config_path.name.replace('.', '_') / module_path.name

    existing = None

    # Build sorted list of (parsed_version, version) pairs, including the current mypy version being used
    parsed_version_pairs = sorted([(parse_mypy_version(v), v) for v in existing_versions])
    if MYPY_VERSION_TUPLE not in [x[0] for x in parsed_version_pairs]:
        insort(parsed_version_pairs, (MYPY_VERSION_TUPLE, mypy_version))

    for parsed_version, version in parsed_version_pairs[::-1]:
        if parsed_version > MYPY_VERSION_TUPLE:
            continue
        output_path = _convert_to_output_path(version)
        if output_path.exists():
            existing = MypyTestTarget(parsed_version, output_path)
            break

    current = MypyTestTarget(MYPY_VERSION_TUPLE, _convert_to_output_path(mypy_version))
    return MypyTestConfig(existing, current)


@pytest.mark.parametrize('config_filename,python_filename', cases)
def test_mypy_results(config_filename: str, python_filename: str, request: pytest.FixtureRequest) -> None:
    input_path = PYDANTIC_ROOT / 'tests/mypy/modules' / python_filename
    config_path = PYDANTIC_ROOT / 'tests/mypy/configs' / config_filename
    test_config = get_test_config(input_path, config_path)

    # Specifying a different cache dir for each configuration dramatically speeds up subsequent execution
    # It also prevents cache-invalidation-related bugs in the tests
    cache_dir = f'.mypy_cache/test-{os.path.splitext(config_filename)[0]}'
    command = [
        str(input_path),
        '--config-file',
        str(config_path),
        '--cache-dir',
        cache_dir,
        '--show-error-codes',
        '--show-traceback',
    ]
    if MYPY_VERSION_TUPLE < (1, 4, 1):
        # I don't know exactly what version this got fixed in, but it definitely works in 1.4.1
        command.append('--disable-recursive-aliases')
    print(f"\nExecuting: mypy {' '.join(command)}")  # makes it easier to debug as necessary
    mypy_out, mypy_err, mypy_returncode = mypy_api.run(command)

    # Need to strip filenames due to differences in formatting by OS
    mypy_out = '\n'.join(['.py:'.join(line.split('.py:')[1:]) for line in mypy_out.split('\n') if line]).strip()
    mypy_out = re.sub(r'\n\s*\n', r'\n', mypy_out)
    if mypy_out:
        print('{0}\n{1:^100}\n{0}\n{2}\n{0}'.format('=' * 100, f'mypy {mypy_version} output', mypy_out))
    assert mypy_err == ''

    input_code = input_path.read_text()

    existing_output_code: Optional[str] = None
    if test_config.existing is not None:
        existing_output_code = test_config.existing.output_path.read_text()
        print(f'Comparing output with {test_config.existing.output_path}')

    merged_output = merge_python_and_mypy_output(input_code, mypy_out)

    if merged_output == existing_output_code:
        # Test passed, no changes needed
        pass
    elif request.config.getoption('update_mypy'):
        test_config.current.output_path.parent.mkdir(parents=True, exist_ok=True)
        test_config.current.output_path.write_text(merged_output)
    else:
        assert existing_output_code is not None, 'No output file found, run `make test-mypy-update` to create it'
        assert merged_output == existing_output_code
        expected_returncode = get_expected_return_code(existing_output_code)
        assert mypy_returncode == expected_returncode


def test_bad_toml_config() -> None:
    full_config_filename = 'tests/mypy/configs/pyproject-plugin-bad-param.toml'
    full_filename = 'tests/mypy/modules/success.py'

    # Specifying a different cache dir for each configuration dramatically speeds up subsequent execution
    # It also prevents cache-invalidation-related bugs in the tests
    cache_dir = '.mypy_cache/test-pyproject-plugin-bad-param'
    command = [full_filename, '--config-file', full_config_filename, '--cache-dir', cache_dir, '--show-error-codes']
    if MYPY_VERSION_TUPLE >= (0, 990):
        command.append('--disable-recursive-aliases')
    print(f"\nExecuting: mypy {' '.join(command)}")  # makes it easier to debug as necessary
    with pytest.raises(ValueError) as e:
        mypy_api.run(command)

    assert str(e.value) == 'Configuration value must be a boolean for key: init_forbid_extra'


def get_expected_return_code(source_code: str) -> int:
    if re.findall(r'^\s*# MYPY:', source_code, flags=re.MULTILINE):
        return 1
    return 0


@pytest.mark.parametrize('module', ['dataclass_no_any', 'plugin_success', 'plugin_success_baseConfig'])
@pytest.mark.filterwarnings('ignore:.*is deprecated.*:DeprecationWarning')
@pytest.mark.filterwarnings('ignore:.*are deprecated.*:DeprecationWarning')
def test_success_cases_run(module: str) -> None:
    """
    Ensure the "success" files can actually be executed
    """
    importlib.import_module(f'tests.mypy.modules.{module}')


def test_explicit_reexports():
    from pydantic import __all__ as root_all
    from pydantic.deprecated.tools import __all__ as tools
    from pydantic.main import __all__ as main
    from pydantic.networks import __all__ as networks
    from pydantic.types import __all__ as types

    for name, export_all in [('main', main), ('network', networks), ('tools', tools), ('types', types)]:
        for export in export_all:
            assert export in root_all, f'{export} is in {name}.__all__ but missing from re-export in __init__.py'


def test_explicit_reexports_exist():
    import pydantic

    for name in pydantic.__all__:
        assert hasattr(pydantic, name), f'{name} is in pydantic.__all__ but missing from pydantic'


@pytest.mark.parametrize(
    'v_str,v_tuple',
    [
        ('0', (0,)),
        ('0.930', (0, 930)),
        ('0.940+dev.04cac4b5d911c4f9529e6ce86a27b44f28846f5d.dirty', (0, 940)),
    ],
)
def test_parse_mypy_version(v_str, v_tuple):
    assert parse_mypy_version(v_str) == v_tuple


def merge_python_and_mypy_output(source_code: str, mypy_output: str) -> str:
    merged_lines = [(line, False) for line in source_code.splitlines()]

    for line in mypy_output.splitlines()[::-1]:
        if not line:
            continue
        try:
            line_number, message = line.split(':', maxsplit=1)
            merged_lines.insert(int(line_number), (f'# MYPY: {message.strip()}', True))
        except ValueError:
            # This could happen due to lack of a ':' in `line`, or the pre-':' contents not being a number
            # Either way, put all such lines at the top of the file
            merged_lines.insert(0, (f'# MYPY: {line.strip()}', True))

    merged_lines = [line for line, is_mypy in merged_lines if is_mypy or not line.strip().startswith('# MYPY: ')]
    return '\n'.join(merged_lines) + '\n'

import dataclasses
import importlib
import os
import re
import sys
from bisect import insort
from pathlib import Path
from typing import Any, List, Optional, Tuple, Union

import pytest

try:
    from mypy import api as mypy_api
    from mypy.version import __version__ as mypy_version

    from pydantic.version import parse_mypy_version

except ImportError:
    mypy_api = None
    mypy_version = None

    parse_mypy_version = lambda _: (0,)  # noqa: E731


MYPY_VERSION_TUPLE = parse_mypy_version(mypy_version)
PYDANTIC_ROOT = Path(__file__).parent.parent.parent

pytestmark = pytest.mark.skipif(
    '--test-mypy' not in sys.argv
    and os.environ.get('PYCHARM_HOSTED') != '1',  # never skip when running via the PyCharm runner
    reason='Test only with "--test-mypy" flag',
)

# This ensures mypy can find the test files, no matter where tests are run from:
os.chdir(Path(__file__).parent.parent.parent)


@dataclasses.dataclass
class MypyCasesBuilder:
    configs: Union[str, List[str]]
    modules: Union[str, List[str]]
    marks: Any = None

    def build(self) -> List[Union[Tuple[str, str], Any]]:
        """
        Produces the cartesian product of the configs and modules, optionally with marks.
        """
        if isinstance(self.configs, str):
            self.configs = [self.configs]
        if isinstance(self.modules, str):
            self.modules = [self.modules]
        built_cases = []
        for config in self.configs:
            for module in self.modules:
                built_cases.append((config, module))
        if self.marks is not None:
            built_cases = [pytest.param(config, module, marks=self.marks) for config, module in built_cases]
        return built_cases


cases = (
    # No plugin
    MypyCasesBuilder(
        ['mypy-default.ini', 'pyproject-default.toml'],
        ['fail1.py', 'fail2.py', 'fail3.py', 'fail4.py', 'pydantic_settings.py'],
    ).build()
    + MypyCasesBuilder(
        ['mypy-default.ini', 'pyproject-default.toml'],
        'success.py',
        pytest.mark.skipif(MYPY_VERSION_TUPLE > (1, 0, 1), reason='Need to handle some more things for mypy >=1.1.1'),
    ).build()
    + MypyCasesBuilder(
        'mypy-default.ini', ['plugin_success.py', 'plugin_success_baseConfig.py', 'metaclass_args.py']
    ).build()
    # Default plugin config
    + MypyCasesBuilder(
        ['mypy-plugin.ini', 'pyproject-plugin.toml'],
        [
            'plugin_success.py',
            'plugin_fail.py',
            'plugin_success_baseConfig.py',
            'plugin_fail_baseConfig.py',
            'pydantic_settings.py',
        ],
    ).build()
    # Strict plugin config
    + MypyCasesBuilder(
        ['mypy-plugin-strict.ini', 'pyproject-plugin-strict.toml'],
        [
            'plugin_success.py',
            'plugin_fail.py',
            'fail_defaults.py',
            'plugin_success_baseConfig.py',
            'plugin_fail_baseConfig.py',
        ],
    ).build()
    # One-off cases
    + [
        ('mypy-plugin.ini', 'custom_constructor.py'),
        ('mypy-plugin.ini', 'generics.py'),
        ('mypy-plugin.ini', 'root_models.py'),
        ('mypy-plugin-strict.ini', 'plugin_default_factory.py'),
        ('mypy-plugin-strict-no-any.ini', 'dataclass_no_any.py'),
        ('mypy-plugin-very-strict.ini', 'metaclass_args.py'),
        pytest.param(
            'pyproject-default.toml',
            'computed_fields.py',
            marks=pytest.mark.skipif(
                sys.version_info < (3, 8),
                reason='cached_property is only available in Python 3.8+, errors are different with mypy 0.971',
            ),
        ),
    ]
)


@dataclasses.dataclass
class MypyTestTarget:
    parsed_mypy_version: Tuple[int, ...]
    output_path: Path


@dataclasses.dataclass
class MypyTestConfig:
    existing: Optional[MypyTestTarget]  # the oldest target with an output that is no older than the installed mypy
    current: MypyTestTarget  # the target for the current installed mypy


def get_test_config(module_path: Path, config_path: Path) -> MypyTestConfig:
    outputs_dir = PYDANTIC_ROOT / 'tests/mypy/outputs'
    outputs_dir.mkdir(exist_ok=True)
    existing_versions = [
        x.name for x in outputs_dir.iterdir() if x.is_dir() and re.match(r'[0-9]+(?:\.[0-9]+)*', x.name)
    ]

    def _convert_to_output_path(v: str) -> Path:
        return outputs_dir / v / config_path.name.replace('.', '_') / module_path.name

    existing = None

    # Build sorted list of (parsed_version, version) pairs, including the current mypy version being used
    parsed_version_pairs = sorted([(parse_mypy_version(v), v) for v in existing_versions])
    if MYPY_VERSION_TUPLE not in [x[0] for x in parsed_version_pairs]:
        insort(parsed_version_pairs, (MYPY_VERSION_TUPLE, mypy_version))

    for parsed_version, version in parsed_version_pairs[::-1]:
        if parsed_version > MYPY_VERSION_TUPLE:
            continue
        output_path = _convert_to_output_path(version)
        if output_path.exists():
            existing = MypyTestTarget(parsed_version, output_path)
            break

    current = MypyTestTarget(MYPY_VERSION_TUPLE, _convert_to_output_path(mypy_version))
    return MypyTestConfig(existing, current)


@pytest.mark.parametrize('config_filename,python_filename', cases)
def test_mypy_results(config_filename: str, python_filename: str, request: pytest.FixtureRequest) -> None:
    input_path = PYDANTIC_ROOT / 'tests/mypy/modules' / python_filename
    config_path = PYDANTIC_ROOT / 'tests/mypy/configs' / config_filename
    test_config = get_test_config(input_path, config_path)

    # Specifying a different cache dir for each configuration dramatically speeds up subsequent execution
    # It also prevents cache-invalidation-related bugs in the tests
    cache_dir = f'.mypy_cache/test-{os.path.splitext(config_filename)[0]}'
    command = [
        str(input_path),
        '--config-file',
        str(config_path),
        '--cache-dir',
        cache_dir,
        '--show-error-codes',
        '--show-traceback',
    ]
    if MYPY_VERSION_TUPLE < (1, 4, 1):
        # I don't know exactly what version this got fixed in, but it definitely works in 1.4.1
        command.append('--disable-recursive-aliases')
    print(f"\nExecuting: mypy {' '.join(command)}")  # makes it easier to debug as necessary
    mypy_out, mypy_err, mypy_returncode = mypy_api.run(command)

    # Need to strip filenames due to differences in formatting by OS
    mypy_out = '\n'.join(['.py:'.join(line.split('.py:')[1:]) for line in mypy_out.split('\n') if line]).strip()
    mypy_out = re.sub(r'\n\s*\n', r'\n', mypy_out)
    if mypy_out:
        print('{0}\n{1:^100}\n{0}\n{2}\n{0}'.format('=' * 100, f'mypy {mypy_version} output', mypy_out))
    assert mypy_err == ''

    input_code = input_path.read_text()

    existing_output_code: Optional[str] = None
    if test_config.existing is not None:
        existing_output_code = test_config.existing.output_path.read_text()
        print(f'Comparing output with {test_config.existing.output_path}')

    merged_output = merge_python_and_mypy_output(input_code, mypy_out)

    if merged_output == existing_output_code:
        # Test passed, no changes needed
        pass
    elif request.config.getoption('update_mypy'):
        test_config.current.output_path.parent.mkdir(parents=True, exist_ok=True)
        test_config.current.output_path.write_text(merged_output)
    else:
        assert existing_output_code is not None, 'No output file found, run `make test-mypy-update` to create it'
        assert merged_output == existing_output_code
        expected_returncode = get_expected_return_code(existing_output_code)
        assert mypy_returncode == expected_returncode


def test_bad_toml_config() -> None:
    full_config_filename = 'tests/mypy/configs/pyproject-plugin-bad-param.toml'
    full_filename = 'tests/mypy/modules/success.py'

    # Specifying a different cache dir for each configuration dramatically speeds up subsequent execution
    # It also prevents cache-invalidation-related bugs in the tests
    cache_dir = '.mypy_cache/test-pyproject-plugin-bad-param'
    command = [full_filename, '--config-file', full_config_filename, '--cache-dir', cache_dir, '--show-error-codes']
    if MYPY_VERSION_TUPLE >= (0, 990):
        command.append('--disable-recursive-aliases')
    print(f"\nExecuting: mypy {' '.join(command)}")  # makes it easier to debug as necessary
    with pytest.raises(ValueError) as e:
        mypy_api.run(command)

    assert str(e.value) == 'Configuration value must be a boolean for key: init_forbid_extra'


def get_expected_return_code(source_code: str) -> int:
    if re.findall(r'^\s*# MYPY:', source_code, flags=re.MULTILINE):
        return 1
    return 0


@pytest.mark.parametrize('module', ['dataclass_no_any', 'plugin_success', 'plugin_success_baseConfig'])
@pytest.mark.filterwarnings('ignore:.*is deprecated.*:DeprecationWarning')
@pytest.mark.filterwarnings('ignore:.*are deprecated.*:DeprecationWarning')
def test_success_cases_run(module: str) -> None:
    """
    Ensure the "success" files can actually be executed
    """
    importlib.import_module(f'tests.mypy.modules.{module}')


def test_explicit_reexports():
    from pydantic import __all__ as root_all
    from pydantic.deprecated.tools import __all__ as tools
    from pydantic.main import __all__ as main
    from pydantic.networks import __all__ as networks
    from pydantic.types import __all__ as types

    for name, export_all in [('main', main), ('network', networks), ('tools', tools), ('types', types)]:
        for export in export_all:
            assert export in root_all, f'{export} is in {name}.__all__ but missing from re-export in __init__.py'


def test_explicit_reexports_exist():
    import pydantic

    for name in pydantic.__all__:
        assert hasattr(pydantic, name), f'{name} is in pydantic.__all__ but missing from pydantic'


@pytest.mark.parametrize(
    'v_str,v_tuple',
    [
        ('0', (0,)),
        ('0.930', (0, 930)),
        ('0.940+dev.04cac4b5d911c4f9529e6ce86a27b44f28846f5d.dirty', (0, 940)),
    ],
)
def test_parse_mypy_version(v_str, v_tuple):
    assert parse_mypy_version(v_str) == v_tuple


def merge_python_and_mypy_output(source_code: str, mypy_output: str) -> str:
    merged_lines = [(line, False) for line in source_code.splitlines()]

    for line in mypy_output.splitlines()[::-1]:
        if not line:
            continue
        try:
            line_number, message = line.split(':', maxsplit=1)
            merged_lines.insert(int(line_number), (f'# MYPY: {message.strip()}', True))
        except ValueError:
            # This could happen due to lack of a ':' in `line`, or the pre-':' contents not being a number
            # Either way, put all such lines at the top of the file
            merged_lines.insert(0, (f'# MYPY: {line.strip()}', True))

    merged_lines = [line for line, is_mypy in merged_lines if is_mypy or not line.strip().startswith('# MYPY: ')]
    return '\n'.join(merged_lines) + '\n'
