GitLab/Workflows/Squashing

Every merge request in GitLab contains a check box titled "Squash commits when merge request is accepted." If this box is ticked all commits of a particular merge request will be combined into a single commit on the merge-request's target branch. This box is ticked by default and committers are encouraged to adopt a squash workflow.

Benefits edit

The squash workflow has benefits for developers, committers, deployers and operators.

For developers edit

Developers should be primarily concerned with writing, reviewing, and merging in hosted repositories. Complicated git workflows prevent developers from getting work done. A squash workflow simplifies the day-to-day work of merge request authors.

  1. Reduces cognitive overhead Folks who write merge requests should be primarily concerned with the content of their merge requests. Git provides benefits during development for developers who check in their code early and often. Additionally, sharing work publicly eases the process of integration. Merge request history may become cluttered over time: that's fine. If we squash all the development commits when we integrate a merge request into mainline then all the commits that were beneficial for development but not beneficial for operations go away.
  2. Avoids casual --force use There are many horror stories about developers force pushing to the wrong branch. Our workflow should discourage the casual use of force push. If we use a squash merge workflow developers can make changes to merge requests without using force push and without cluttering the mainline branch.

With squashed commits, the final commit message gets created by Gitlab, even if the merge request contains a single commit. To control the final commit message, developers need to take care of the merge request title and description too.

For operators edit

Operators are people interacting with repositories to run them in a local or production-like environment, do automated or manual testing, package changes, build changelogs, or understand project history. Developers and operators may be the same people at different times. Operators are primarily concerned with understandable git history.

  1. Avoids criss-cross merges. Criss-cross merges occur when two branches merge each other. As a result there is no single ancestor of both branches — there are 2 ancestors. This may occur, for example, when a merge request author does git pull main before pushing to their feature branch; when the subsequent branch is committed back to mainline we end up with a criss-cross merge. Git is occasionally unable to resolve merge, revert, or patch conflicts when criss-cross merges are present. The inability of git to successfully resolve this situation may result in avoidable semantic error by human operators. This is easily side-stepped by using a squash workflow. Since all work is squashed, no merge from mainline into the feature branch will exist in the final history.
  2. Filters meaningless commits. During development and review it's common to add fixup commits like, fixup! typo in comment. These commits are necessary for development but they would be a vector for bugs in production. Likewise, these commits are not meaningful history. These types of commit messages add noise when you are trying to use git bisect or git blame and may slow recovery of continuous integration or production when trying to revert. Squashing these types of commits before merging with a mainline branch prevents these problems.

When not to use squash edit

Feature branches may contain several commits that need to remain separate after merge into mainline. If the logical structure of your change dictates multiple commits, you may use interactive rebase to achieve the desired history and force push to your work branch before merge. This is an advanced but supported workflow in GitLab.

In practice, the squash strategy chosen should maintain the principle that a commit in a mainline branch’s history should make sense on its own.

What about merge commits? edit

The squash workflow can be used regardless of the merge strategy. The merge strategy will determine whether or not there is a merge-commit on the mainline branch after squashing.

If the "fast-forward" merge strategy is chosen along with squashing then there will be one commit on the mainline branch after merging a merge-request that will comprise all changes inside that merge request. If either of "merge commit" or "merge commit with semi-linear history" is the merge strategy, then squashing a merge request will create two commits on the mainline branch: a commit with all changes from a merge request and a merge commit showing that a merge request has been merged.