Bringing changes from a feature branch into your main codebase shapes the project's history and can influence how teams collaborate or review code. Developers regularly weigh whether to use git merge, git rebase, or a squash merge to combine work. Each method changes the repository's commit graph in its own way and affects how others interact with the codebase. The Git Project documentation points out that these operations handle divergent histories and commit ancestry differently.
How Each Git Method Alters History
Using git merge keeps every commit from the feature branch and adds a new merge commit to connect the two development lines. This method preserves the full context of how changes developed, which helps when working with long-lived or shared branches. The resulting history lists all individual commits, including minor fixes, so it's easier to track where changes came from. When histories have diverged, a merge commit with two parents records the relationship between branch tips. If the target branch hasn't moved since the feature branch started, a fast-forward merge simply advances the branch pointer without creating a merge commit.
With git rebase, the branch's commits are replayed on top of the target branch, creating new commits with different hashes. This results in a straight, linear history without merge commits. Rebasing helps clean up your own branch before sharing, as it avoids adding extra merge commits to the main branch. Since rebase rewrites commit history by making replacement commits, commit IDs change. Git documentation cautions that rewriting commits already shared with others can force collaborators to resolve local history conflicts. Rebasing works best for private or coordinated branches before merging.
Squash merging, done with git merge --squash, combines all the feature branch's commits into a single new commit on the target branch. This is useful when you want the main branch to show one logical change per feature, making the history easier to follow. After a squash merge, Git doesn't treat the feature branch as merged, so you may need to delete it with git branch -D once the work is integrated. The resulting commit has a new identity and doesn't keep the branch’s intermediate commits in the target branch’s first-parent history. Unlike a standard merge, Git doesn't record the feature tip as an ancestor through a merge commit, which can affect later branch management.
Practical Example: Starting Point and Commands
Imagine your repository begins with one commit on main, then a feature branch adds three commits, and main gets another commit. This setup creates two separate lines of development. Running git log --oneline --graph --all will display both branches and their commits, showing a typical scenario where a team develops features while others update the main branch. These structural details of Git’s commit graph are covered in the official documentation.
Comparing Merge, Rebase, and Squash
MethodCommandHistory ShapeCommits on MainFeature Commits KeptRewrites CommitsConflictsSafe for Shared BranchesMerge | git merge feature | Branches and joins | 6 (includes merge commit) | All, unchanged | No | Once | Yes
Rebase | git rebase main | Straight line | 5 | All, new hashes | Yes | Possibly per commit | No, needs force push
Squash | git merge --squash feature | Straight line | 3 | None, replaced by one | No | Once | Yes, on target branch
GitHub's pull request interface gives you these same choices, letting you create a merge commit, squash and merge, or rebase and merge. Their documentation explains each approach in detail. The Git Project documentation highlights that merge keeps development context, rebase creates a linear history by rewriting commit identities, and squash gives you one integration commit but drops intermediate history on the target branch.
Handling Conflicts and Undoing Changes
Merge and squash methods ask you to resolve conflicts once for the combined changes. Rebase may pause at each conflicting commit, so you resolve issues step by step. To undo a merge, use git reset --hard ORIG_HEAD right after the operation. For rebases, the reflog lets you return to the previous state with git reset --hard feature@{1}. Squash merges are just regular commits, so you can undo them like any other commit.
Best Practices for Choosing a Method
For shared or long-lived branches, merging keeps all context and is safest for teamwork. Rebasing is best for updating your own branch before sharing, as it keeps history linear and avoids extra merge commits. Squash merging works well when you want a single commit per feature on the main branch, making the log concise and easier to read. Teams often use these strategies together: rebase your feature branch to stay current, then squash merge into main for clarity. The Git Project documentation points out that reviewability, rollback granularity, provenance, branch-sharing habits, and how the project handles pull requests should guide the choice, since “cleaner” history depends on project needs.
Key Rule: Avoid Rebasing Shared History
Never rebase commits that others have already pulled. Rebasing changes commit hashes, which can confuse teammates and create duplicate commits. If you must push a rebased branch, use --force-with-lease to avoid overwriting others' work. On main or other shared branches, stick with merge or squash to keep history consistent and reliable. The operational risk of rebasing comes up when other developers have already based work on or pulled those commits, and repository contribution policies—such as blocking force-pushes to protected branches—shape the real impact more than the command name itself.
Additional Tips and Resources
When pulling changes, git pull --rebase helps keep your local commits on top of the latest remote changes, cutting down on unnecessary merge commits. To make this the default, set git config --global pull.rebase true. For more on these commands and their options, check the official git merge, git rebase, and git pull documentation. For advanced workflows and rewriting history, the Pro Git book covers these topics in depth.
Summary Table: When to Use Each Method
SituationRecommended MethodReasonFinishing a feature for a shared main branch | Merge or squash | Preserves history for others
Updating your private branch | Rebase | Linear history, no extra merges
Cleaning up "wip" and minor commits | Squash or interactive rebase | One clean commit per feature
Long-lived or team branches | Merge | Keeps context, safe for collaboration
Pulling before pushing | git pull --rebase | Avoids unnecessary merge commits
Need every commit for debugging | Merge or rebase | Squash hides individual steps
For more practical Git tips and tested workflows, see the original guide published on mfaysal.com. The article's examples were run on Git 2.47.3 using throwaway repositories with fixed commit dates to keep results consistent across each demonstration. The Git Project documentation confirms that no government regulator, court, or statutory framework dictates the choice among Git merge, rebase, and squash; these are version-control operations managed by repository policies set by project maintainers or hosting platforms.
