Skip to main content

Git Branches with Different Commit Histories

To keep a long-running dev branch with a detailed commit history while periodically merging changes into a main branch with a cleaner, simplified history, using git merge --squash is a viable approach, but whether it's the best choice depends on your goals and workflow.

Goals and Assumptions

  • Long-running dev branch: You want dev to retain its full, detailed commit history for development, debugging, and collaboration.
  • Clean main branch: You want main to have a simplified history, ideally with fewer commits (e.g., one commit per feature or release).
  • Merging from dev to main: You plan to periodically integrate changes from dev into main without bringing the full dev history.

Using git merge --squash

The --squash option in git merge combines all changes from the source branch (dev) into a single commit on the target branch (main), discarding the individual commit history from dev. This aligns with your goal of keeping a clean main branch.

Workflow with --squash

  1. Work on dev:

    • Make commits on dev as usual, building up a detailed history.

      These commands demonstrate typical development work with detailed commit messages on the dev branch:

      git checkout dev
      git commit -m "Add feature X"
      git commit -m "Fix bug in feature X"
      git commit -m "Refactor feature X"
  2. Merge to main with --squash:

    • When ready to integrate changes into main, use --squash to collapse all dev changes since the last merge into a single commit.

      This sequence shows how to squash multiple dev commits into a single main commit:

      git checkout main
      git merge --squash dev
      git commit -m "Add feature X with all changes"
    • This creates a single commit on main containing all changes from dev since the branches diverged, without including dev's commit history.

  3. Continue Development:

    • After squashing, main and dev have the same content but different histories. To keep dev up-to-date with main (especially if main receives other changes), rebase or merge main back into dev:

      This command keeps dev synchronized with main using rebase to maintain a linear history:

      git checkout dev
      git rebase main
      • Alternatively, merge main into dev to avoid rewriting history:

        This approach preserves the commit history without rewriting, which is safer for shared branches:

        git checkout dev
        git merge main

Pros of Using --squash

  • Clean main History: Each merge results in a single, meaningful commit on main, making the history easier to read for releases or audits.
  • Preserves dev History: The detailed commit history in dev remains intact, useful for debugging or tracking incremental changes.
  • Simple Workflow: Straightforward for solo developers or small teams, as it avoids complex rebasing.
  • No History Pollution: main doesn't inherit the granular, potentially messy commits from dev.

Cons of Using --squash

  • Loss of Granular History in main: If you need to trace specific changes in main (e.g., for debugging), the squashed commit lacks the detailed context preserved in dev.
  • Manual Commit Messages: You must write a new commit message for each squashed commit, which can be tedious if merging frequently.
  • Potential for Conflicts: If main diverges significantly (e.g., due to hotfixes or other merges), rebasing or merging main back into dev may lead to conflicts.
  • Collaboration Challenges: If dev is shared with a team, squashing can make it harder to track changes when reviewing main, as the commit history is condensed.

Alternatives to --squash

If --squash doesn't fully meet your needs, consider these alternatives for merging dev into main while maintaining different histories:

1. Regular Merge with Curated dev Commits

  • Approach: Clean up dev's history before merging into main using git rebase -i to squash or rewrite commits into a more concise form, then perform a regular merge.

    This workflow allows you to curate your commit history before merging:

    git checkout dev
    git rebase -i <commit-before-dev-diverged>
    # Squash or edit commits in the interactive rebase interface
    git checkout main
    git merge dev
  • Pros:

    • Allows you to curate dev's history before merging, retaining some meaningful commits in main.
    • Maintains a direct connection between dev and main commits, making it easier to trace changes.
  • Cons:

    • Rewrites dev's history, which can disrupt collaboration if dev is shared.
    • Requires manual effort to organize commits during rebase.

2. Feature Branches with Squashing

  • Approach: Instead of squashing the entire dev branch, create feature branches off dev for specific tasks. Squash these feature branches when merging into main, keeping dev as the long-running integration branch.
    git checkout -b feature-x dev
    git commit -m "Work on feature X"
    git checkout main
    git merge --squash feature-x
    git commit -m "Add feature X"
    git checkout dev
    git merge feature-x # Optionally merge feature back into dev
  • Pros:
    • Keeps dev's history intact while allowing granular squashing per feature.
    • Scales better for teams, as feature branches isolate changes.
  • Cons:
    • Adds overhead of managing multiple feature branches.
    • Requires discipline to keep dev and feature branches in sync.

3. Cherry-Pick for Curated History

  • Approach: Selectively cherry-pick specific commits from dev into main to build a curated history.
    git checkout main
    git cherry-pick <commit-hash>
  • Pros:
    • Gives precise control over which commits appear in main.
    • Avoids rewriting dev's history.
  • Cons:
    • Time-consuming for large numbers of commits.
    • Risk of missing important changes if not carefully managed.

4. Gitflow-Inspired Workflow

  • Approach: Use a release branch to stage changes from dev for main. Squash or rebase the release branch before merging into main.
    git checkout -b release-x.x dev
    git rebase -i <commit-before-dev-diverged>
    git checkout main
    git merge release-x.x
    git checkout dev
    git merge main
  • Pros:
    • Provides a clear separation between development, staging, and production.
    • Allows testing of squashed changes before merging into main.
  • Cons:
    • More complex, with additional branches to manage.
    • Still requires rebasing or squashing for a clean main history.

Recommendation

For your use case—a long-running dev branch with periodic merges into a clean mainusing git merge --squash is a good choice if:

  • You prioritize a very clean main history with one commit per major update or feature.
  • You're comfortable writing summary commit messages for squashed commits.
  • Your team is small or you're working solo, minimizing collaboration issues.

However, consider these adjustments to optimize the workflow:

  • Use Feature Branches: For larger features, work on short-lived feature branches off dev, squash them when merging into dev or main, and keep dev as an integration branch with detailed history. This balances granularity in dev and cleanliness in main.
  • Automate Commit Messages: When squashing, use tools like git log --oneline to summarize dev's changes for the main commit message.
  • Regularly Sync dev with main: After each squash merge, merge main back into dev (git checkout dev; git merge main) to avoid conflicts and keep dev up-to-date. Avoid rebasing dev if it's shared with others, as it rewrites history.
  • Tag Releases: After merging into main, tag the commit (e.g., git tag v1.0) to mark releases, making it easier to track milestones.

Example Workflow

  1. Work on dev:
    git checkout dev
    git commit -m "Add feature X"
    git commit -m "Fix bug in feature X"
  2. Squash-merge into main:
    git checkout main
    git merge --squash dev
    git commit -m "Add feature X with all changes"
    git tag v1.0
  3. Sync dev with main:
    git checkout dev
    git merge main
  4. Continue development on dev.

Additional Considerations

  • Collaboration: If dev is shared, communicate with your team about squash merges to avoid confusion, as squashed commits in main won't directly reference dev's commits.
  • Backup: Before squashing, create a backup branch (git branch dev-backup dev) to preserve history in case of mistakes.
  • Conflict Management: If conflicts arise when merging main into dev, resolve them carefully and test thoroughly.
  • Tools: Use git log --graph --oneline --all to visualize the diverging histories of dev and main.

Resources

For further details, refer to these resources:

If you anticipate frequent merges or complex features, consider experimenting with feature branches or a release branch to reduce the risk of conflicts and improve traceability.