Thoughts on

A blog of various opinions and ideas on development


Git: When and When not to Rewrite History in Git-flow

It’s depends…

In collaborative software development, Git is a powerful tool that helps manage changes across a project. While using Git, one key principle stands out: Don’t rewrite shared history. This maxim ensures that our codebase remains traceable and understandable to all team members. But how do we apply this principle effectively, especially in the context of the git-flow branching model? Let’s delve into it.

Understanding “Rewriting Shared History”

In Git, the term “history” refers to the sequence of commits. “Shared history” encompasses all the commits seen and accessed by others. To “rewrite” means to alter this history - be it changing commit messages, squashing multiple commits into a singular one, or even deleting commits. Such alterations on shared branches can lead to confusion and incompatible and divergent branches

The Git-flow Context

For this discussion, we’re assuming the use of the git-flow branching model. Git-flow provides a structured way of managing features, releases, and hotfixes. With multiple branch types involved, it’s crucial to know when and how to safely squash commits or when to avoid it.

Acceptable Scenarios for Squashing:

  1. feature -> develop: Regular feature work usually involves multiple intermediate commits. Once the feature is complete and ready for integration, it’s okay to squash these commits for clarity. The key here is to delete the feature branch immediately after merging to avoid any future confusion.
  2. feature -> release/*: There are times when a release requires scope extensions or bug fixes. In such cases, squash the related commits in the feature branch before merging them into the release branch. Once again, promptly delete the feature branch post-merge.
  3. feature -> main: Hotfixes are urgent fixes applied directly to the main branch. Since these are typically singular, focused changes, it’s reasonable to squash related commits for clarity. Ensure to delete the feature/hotfix branch once the merge is done.

Scenarios Where Squashing is Unadvisable:

In certain contexts within git-flow, squashing isn’t the right choice. Merge commits provide a more transparent and traceable history in these scenarios.

  1. release/{*,current} -> main: Many times production is only released from the main branch, and a crucial part of that is merging from your release accumulator branch into main. Few things, using merge commits here ensures that the sequence and context of changes remain intact, and you have the full history of what happened and when on the release branch. But also this is shared history, it shouldn’t be squashed.
  2. main -> release/{*,next}: When propagating recently released main changes to future release branches, we would like to maintain that full history, merge commits serve this purpose well. Again as both of these branches are shared history, they shouldn’t be squashed.
  3. main -> develop: When hotfixes and scope creep are initially introduced they should first be introduced to the releases and/or main branches, and then be introduced to develop by having main merged back to develop. Merge commits help trace these sync points clearly. Since these are shared branches they should not be squashed.

In Conclusion

When using Git and adhering to the git-flow model, clarity and traceability are paramount. By understanding when to squash commits and when not to, we pave the way for smoother collaboration and a comprehensible commit history.

tags: Git - VersionControl - Collaboration - BestPractices - PullRequests - CodeMerge - Teamwork - DeveloperTools - SoftwareDevelopment - CodingGuidelines