<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Amir Dhaouadi</title>
    <description>The latest articles on DEV Community by Amir Dhaouadi (@adcpd).</description>
    <link>https://dev.to/adcpd</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2109677%2F8517960e-7790-4425-b036-4ba3857da82b.png</url>
      <title>DEV Community: Amir Dhaouadi</title>
      <link>https://dev.to/adcpd</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/adcpd"/>
    <language>en</language>
    <item>
      <title>GitLab: Require Squash Commits for Pull Requests to the “develop” Branch</title>
      <dc:creator>Amir Dhaouadi</dc:creator>
      <pubDate>Mon, 26 Oct 2026 16:35:59 +0000</pubDate>
      <link>https://dev.to/adcpd/gitlab-require-squash-commits-for-pull-requests-to-the-develop-branch-22lf</link>
      <guid>https://dev.to/adcpd/gitlab-require-squash-commits-for-pull-requests-to-the-develop-branch-22lf</guid>
      <description>&lt;h2&gt;
  
  
  Context
&lt;/h2&gt;

&lt;p&gt;On GitLab, squash enforcement has to go through CI, because the native setting applies to the whole project. In &lt;em&gt;Settings → Merge requests → Squash commits when merging&lt;/em&gt;, the &lt;strong&gt;Require&lt;/strong&gt; option would also squash the &lt;code&gt;develop → master&lt;/code&gt; release MRs, which must keep their history.&lt;/p&gt;

&lt;p&gt;GitLab does have an advantage over GitHub: every MR carries a "Squash commits when merge request is accepted" checkbox, readable in CI through &lt;code&gt;CI_MERGE_REQUEST_SQUASH_ON_MERGE&lt;/code&gt; and editable through the API. The three options below rely on jobs that only run for MRs into &lt;code&gt;develop&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Shared prerequisite: enable &lt;strong&gt;Pipelines must succeed&lt;/strong&gt; (&lt;em&gt;Settings → Merge requests&lt;/em&gt;). Without it, a failed job shows a red cross but does not prevent the merge. Squashing must also be allowed (not set to "Do not allow").&lt;/p&gt;

&lt;h2&gt;
  
  
  Option 1 — Block when the squash box is unticked
&lt;/h2&gt;

&lt;p&gt;The job reads the checkbox when the pipeline starts and fails if it is not ticked. The pipeline turns red, so the Merge button stays greyed out.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;squash-required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;stage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.pre&lt;/span&gt;
  &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;public.ecr.aws/docker/library/alpine:3.20&lt;/span&gt;
  &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
      &lt;span class="s"&gt;if [ "$CI_MERGE_REQUEST_SQUASH_ON_MERGE" != "true" ]; then&lt;/span&gt;
        &lt;span class="s"&gt;echo "Merge requests into develop must be squashed: tick 'Squash commits when merge request is accepted', then run a new pipeline."&lt;/span&gt;
        &lt;span class="s"&gt;exit 1&lt;/span&gt;
      &lt;span class="s"&gt;fi&lt;/span&gt;
  &lt;span class="na"&gt;rules&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;$CI_PIPELINE_SOURCE == "merge_request_event" &amp;amp;&amp;amp; $CI_MERGE_REQUEST_TARGET_BRANCH_NAME == "develop"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;stage: .pre&lt;/code&gt; runs the job before all others, so the failure shows up immediately.&lt;/li&gt;
&lt;li&gt;To unblock, the developer ticks the box and starts a &lt;strong&gt;new pipeline&lt;/strong&gt;. Simply retrying the job would reuse the old value of the variable.&lt;/li&gt;
&lt;li&gt;Limitation: the checkbox is only read at startup. Unticking it after a green pipeline allows a merge without squash.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Option 2 — Tick the box automatically through the API
&lt;/h2&gt;

&lt;p&gt;Instead of failing, the job ticks the box on the MR with a call to the GitLab API. GitLab reads the option at merge time, so the squash will be applied. Without a token, or if the call fails, the job falls back to the blocking behaviour of option 1.&lt;/p&gt;

&lt;p&gt;Setup:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create a &lt;em&gt;project access token&lt;/em&gt; (&lt;em&gt;Settings → Access tokens&lt;/em&gt;), role &lt;strong&gt;Developer&lt;/strong&gt;, scope &lt;strong&gt;api&lt;/strong&gt;, with a short expiry.&lt;/li&gt;
&lt;li&gt;Add it under &lt;em&gt;Settings → CI/CD → Variables&lt;/em&gt; as &lt;code&gt;SQUASH_BOT_TOKEN&lt;/code&gt;, &lt;strong&gt;Masked&lt;/strong&gt; but not &lt;em&gt;Protected&lt;/em&gt;, otherwise feature branches do not receive it.&lt;/li&gt;
&lt;li&gt;Replace the job with this one:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;squash-required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;stage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.pre&lt;/span&gt;
  &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;public.ecr.aws/docker/library/alpine:3.20&lt;/span&gt;
  &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
      &lt;span class="s"&gt;if [ "$CI_MERGE_REQUEST_SQUASH_ON_MERGE" = "true" ]; then&lt;/span&gt;
        &lt;span class="s"&gt;exit 0&lt;/span&gt;
      &lt;span class="s"&gt;fi&lt;/span&gt;
      &lt;span class="s"&gt;if [ -n "$SQUASH_BOT_TOKEN" ] \&lt;/span&gt;
        &lt;span class="s"&gt;&amp;amp;&amp;amp; apk add --no-cache curl &amp;gt; /dev/null \&lt;/span&gt;
        &lt;span class="s"&gt;&amp;amp;&amp;amp; curl --fail -sS -X PUT \&lt;/span&gt;
             &lt;span class="s"&gt;--header "PRIVATE-TOKEN: $SQUASH_BOT_TOKEN" \&lt;/span&gt;
             &lt;span class="s"&gt;--data "squash=true" \&lt;/span&gt;
             &lt;span class="s"&gt;"$CI_API_V4_URL/projects/$CI_PROJECT_ID/merge_requests/$CI_MERGE_REQUEST_IID" &amp;gt; /dev/null&lt;/span&gt;
      &lt;span class="s"&gt;then&lt;/span&gt;
        &lt;span class="s"&gt;echo "Squash was not ticked: enabled automatically on !$CI_MERGE_REQUEST_IID."&lt;/span&gt;
        &lt;span class="s"&gt;exit 0&lt;/span&gt;
      &lt;span class="s"&gt;fi&lt;/span&gt;
      &lt;span class="s"&gt;echo "Merge requests into develop must be squashed: tick 'Squash commits when merge request is accepted', then run a new pipeline."&lt;/span&gt;
      &lt;span class="s"&gt;exit 1&lt;/span&gt;
  &lt;span class="na"&gt;rules&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;$CI_PIPELINE_SOURCE == "merge_request_event" &amp;amp;&amp;amp; $CI_MERGE_REQUEST_TARGET_BRANCH_NAME == "develop"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;PUT&lt;/code&gt; call on &lt;code&gt;/projects/:id/merge_requests/:iid&lt;/code&gt; with &lt;code&gt;squash=true&lt;/code&gt; is the same as ticking the box in the UI; the &lt;code&gt;CI_*&lt;/code&gt; variables fill in the URL automatically.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No action left for the developer.&lt;/li&gt;
&lt;li&gt;The token is not protected: anyone who edits &lt;code&gt;.gitlab-ci.yml&lt;/code&gt; in their branch can read it. The Developer role and a short expiry limit the risk.&lt;/li&gt;
&lt;li&gt;MRs from forks do not receive the variable and fall back to blocking.&lt;/li&gt;
&lt;li&gt;The "untick after the pipeline" loophole remains open.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Option 3 — Require a single commit in the MR
&lt;/h2&gt;

&lt;p&gt;The job counts the MR's commits and fails if there is more than one: the developer squashes before merging. This is the equivalent of the GitHub Action, and the only way to close the "untick afterwards" loophole, since the check runs again on every push.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;single-commit&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;stage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.pre&lt;/span&gt;
  &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;public.ecr.aws/docker/library/alpine:3.20&lt;/span&gt;
  &lt;span class="na"&gt;variables&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;GIT_DEPTH&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;  &lt;span class="c1"&gt;# full history, so the MR base commit is available&lt;/span&gt;
  &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;apk add --no-cache git &amp;gt; /dev/null&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
      &lt;span class="s"&gt;HEAD_SHA="${CI_MERGE_REQUEST_SOURCE_BRANCH_SHA:-$CI_COMMIT_SHA}"&lt;/span&gt;
      &lt;span class="s"&gt;COMMITS=$(git rev-list --count "$CI_MERGE_REQUEST_DIFF_BASE_SHA..$HEAD_SHA")&lt;/span&gt;
      &lt;span class="s"&gt;if [ "$COMMITS" -ne 1 ]; then&lt;/span&gt;
        &lt;span class="s"&gt;echo "MRs into develop must contain a single commit (found $COMMITS). Squash it: git reset --soft origin/develop &amp;amp;&amp;amp; git commit &amp;amp;&amp;amp; git push --force-with-lease"&lt;/span&gt;
        &lt;span class="s"&gt;exit 1&lt;/span&gt;
      &lt;span class="s"&gt;fi&lt;/span&gt;
  &lt;span class="na"&gt;rules&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;$CI_PIPELINE_SOURCE == "merge_request_event" &amp;amp;&amp;amp; $CI_MERGE_REQUEST_TARGET_BRANCH_NAME == "develop"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;GitLab does not expose the commit count as a variable: it is computed with &lt;code&gt;git rev-list&lt;/code&gt;, hence &lt;code&gt;GIT_DEPTH: 0&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;CI_MERGE_REQUEST_SOURCE_BRANCH_SHA&lt;/code&gt; is needed with &lt;em&gt;merged results pipelines&lt;/em&gt;, where &lt;code&gt;CI_COMMIT_SHA&lt;/code&gt; is a temporary merge commit.&lt;/li&gt;
&lt;li&gt;Developers have to squash locally, then run &lt;code&gt;git push --force-with-lease&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Comparison and recommendation
&lt;/h2&gt;

&lt;p&gt;Recommendation: &lt;strong&gt;option 2&lt;/strong&gt;, which removes forgotten checkboxes with no effort from developers and falls back to option 1 without a token. Option 3 is only worth it if the "untick afterwards" loophole is a real problem for the team.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Option 1 — Block&lt;/th&gt;
&lt;th&gt;Option 2 — Tick via the API&lt;/th&gt;
&lt;th&gt;Option 3 — Single commit&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Box forgotten&lt;/td&gt;
&lt;td&gt;Merge blocked&lt;/td&gt;
&lt;td&gt;Box ticked automatically&lt;/td&gt;
&lt;td&gt;Not applicable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Developer effort&lt;/td&gt;
&lt;td&gt;Tick, then rerun&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;Squash locally&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Box unticked after the pipeline&lt;/td&gt;
&lt;td&gt;Not detected&lt;/td&gt;
&lt;td&gt;Not detected&lt;/td&gt;
&lt;td&gt;Cannot be bypassed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Prerequisites&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;Token + CI variable&lt;/td&gt;
&lt;td&gt;&lt;code&gt;GIT_DEPTH: 0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Added risk&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;Token readable by developers&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Difference with GitHub: GitHub can restrict the merge method per branch (ruleset), but has no squash checkbox readable in CI. GitLab is the opposite; only option 3 exists on both platforms.&lt;/p&gt;

&lt;p&gt;See &lt;a href="https://dev.to/adcpd/github-obliger-le-squash-des-pull-requests-vers-develop-1kdc"&gt;the Github: Require Squash Commits for Pull Requests to the “develop” Branch&lt;/a&gt;&lt;/p&gt;

</description>
      <category>cicd</category>
      <category>gitlab</category>
    </item>
    <item>
      <title>🔒 GitHub : obliger le squash des pull requests vers develop</title>
      <dc:creator>Amir Dhaouadi</dc:creator>
      <pubDate>Mon, 26 Oct 2026 16:19:54 +0000</pubDate>
      <link>https://dev.to/adcpd/github-obliger-le-squash-des-pull-requests-vers-develop-1kdc</link>
      <guid>https://dev.to/adcpd/github-obliger-le-squash-des-pull-requests-vers-develop-1kdc</guid>
      <description>&lt;h2&gt;
  
  
  Context
&lt;/h2&gt;

&lt;p&gt;On GitLab, a CI job can block an unsquashed MR by reading &lt;code&gt;CI_MERGE_REQUEST_SQUASH_ON_MERGE&lt;/code&gt;.&lt;br&gt;
On GitHub, the same approach is not possible: &lt;strong&gt;there is no "squash" checkbox on the PR&lt;/strong&gt;.&lt;br&gt;
The merge method (Merge commit, Squash, Rebase) is chosen when clicking the button, after CI has run.&lt;/p&gt;

&lt;p&gt;There are two approaches.&lt;/p&gt;
&lt;h2&gt;
  
  
  Option 1 — Ruleset: squash only on &lt;code&gt;develop&lt;/code&gt;
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Settings → General → Pull Requests&lt;/strong&gt;: keep &lt;em&gt;Allow squash merging&lt;/em&gt; and &lt;em&gt;Allow merge commits&lt;/em&gt; enabled
(the repository settings are the master switch; a ruleset can only restrict further).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Settings → Rules → Rulesets → New branch ruleset&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;Target: &lt;code&gt;develop&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Enable &lt;em&gt;Require a pull request before merging&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Allowed merge methods&lt;/em&gt;: &lt;strong&gt;Squash&lt;/strong&gt; only&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;main&lt;/code&gt; / &lt;code&gt;master&lt;/code&gt;: no restriction (or a separate ruleset) to keep merge commits for release PRs.&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;✅ No code, no effort for developers; applies to both the UI and the API.&lt;/li&gt;
&lt;li&gt;⚠️ Some users report that the rule is not always enforced when the repository allows
several methods. &lt;strong&gt;Test it with a PR into &lt;code&gt;develop&lt;/code&gt; before relying on it.&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Option 2 — GitHub Actions: a single commit per PR
&lt;/h2&gt;

&lt;p&gt;The PR must contain a single commit: the developer squashes it themselves before merging.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;.github/workflows/squash-required.yml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;squash-required&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;develop&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;types&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;opened&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;synchronize&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;reopened&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;edited&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;single-commit&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Check the PR has a single commit&lt;/span&gt;
        &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;COMMITS&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ github.event.pull_request.commits }}&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;if [ "$COMMITS" -ne 1 ]; then&lt;/span&gt;
            &lt;span class="s"&gt;echo "::error::PRs into develop must contain a single commit (found $COMMITS). Squash it: git reset --soft origin/develop &amp;amp;&amp;amp; git commit &amp;amp;&amp;amp; git push --force-with-lease"&lt;/span&gt;
            &lt;span class="s"&gt;exit 1&lt;/span&gt;
          &lt;span class="s"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For the check to &lt;strong&gt;block&lt;/strong&gt; the merge: in the &lt;code&gt;develop&lt;/code&gt; ruleset, enable&lt;br&gt;
&lt;em&gt;Require status checks to pass&lt;/em&gt; and add &lt;code&gt;single-commit&lt;/code&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ Reliable: runs again on every push, cannot be bypassed afterwards (unlike the GitLab checkbox).&lt;/li&gt;
&lt;li&gt;⚠️ Developers must squash locally, then run &lt;code&gt;git push --force-with-lease&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Comparison
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Ruleset (option 1)&lt;/th&gt;
&lt;th&gt;Action (option 2)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;What is enforced&lt;/td&gt;
&lt;td&gt;Merge method = squash&lt;/td&gt;
&lt;td&gt;PR = a single commit&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Code to maintain&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;One workflow&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Developer effort&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;Squash before merging&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reliability&lt;/td&gt;
&lt;td&gt;To be tested on the repository&lt;/td&gt;
&lt;td&gt;Guaranteed if the check is required&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Recommendation
&lt;/h2&gt;

&lt;p&gt;Start with &lt;strong&gt;option 1&lt;/strong&gt;. If it does not behave as expected, add &lt;strong&gt;option 2&lt;/strong&gt;&lt;br&gt;
as a safety net.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.blog/changelog/2024-12-04-pull-request-merge-method-rule-public-preview" rel="noopener noreferrer"&gt;GitHub changelog: merge method rule for pull requests&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/orgs/community/discussions/184410" rel="noopener noreferrer"&gt;Community discussion: "squash only" rulesets not always enforced&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/configuring-pull-request-merges/about-merge-methods-on-github" rel="noopener noreferrer"&gt;GitHub Docs: about merge methods&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>git</category>
      <category>github</category>
      <category>cicd</category>
    </item>
  </channel>
</rss>
