6 minutes
Understanding Divergence in Git
When working with Git, “histories have diverged” means that two branches of a project have evolved independently, resulting in different commit histories. This can happen in various situations and understanding how to handle it is crucial for effective collaboration and keeping a clean project history.
The Concept
Imagine a tree. The trunk represents the original codebase, and branches grow from the trunk, each representing a new feature or development path. When two branches grow independently, they diverge, creating separate paths.
In Git, this happens when:
- Multiple developers work on the same project. Each developer creates their own branch and makes changes independently.
- A branch is pushed to a remote repository, and someone else makes changes to the same branch on the remote. This creates two different versions of the branch.
Resolving Divergence
When you try to merge or rebase these divergent branches, Git will encounter conflicts. Resolving these conflicts means deciding how to combine the changes from both branches. Here are common methods to resolve divergence:
Merge
Merging combines the changes from both branches into a new commit, preserving the history of both branches. This method is often used when you want to keep the commit history intact and show the parallel development paths.
Rebase
Rebasing replays your commits on top of the other branch, creating a linear history. This method is useful when you want to maintain a clean and linear project history, making it easier to follow the development process.
Resolving Divergent Git Histories
Understanding the Conflict
Let’s look at the different types of conflicts that can occur and how to address them.
- Merge Conflicts: These happen when the same lines in a file have been modified differently in both branches. Git cannot automatically determine the correct version, so it requires manual resolution.
- Semantic Conflicts: These are more subtle conflicts that arise from changes that interact unexpectedly. These can be harder to detect and resolve.
Conflict Resolution Techniques
Manual Conflict Resolution:
- Git marks conflict areas in the file with special markers («««<, =======, »»»>).
- Edit the file to combine the changes as desired.
- Stage the resolved file to complete the merge.
Using a Merge Tool:
- Git provides integrations with various merge tools (e.g., meld, kdiff3, vimdiff).
- These tools offer a visual interface for comparing and merging changes.
Git Mergetool:
- A built-in Git command that opens a merge tool for the current conflict.
Rebase (With Caution):
- Rebasing can create a linear history, but it’s more complex and can introduce issues if pushed to a shared repository.
- Only recommended for personal branches or when you’re absolutely certain there are no conflicts.
Let’s Dive into a Conflict Resolution Scenario
Scenario
You’re working on a feature branch called feature-checkout
to implement a new checkout process for your e-commerce application. Another developer is working on a branch called performance-optimization
to improve website speed. Both branches involve modifications to the checkout.js
file.
Conflict Arises
You merge performance-optimization
into your feature-checkout
branch. A merge conflict occurs in checkout.js
.
Conflict Resolution
Identify the Conflict:
- Use
git status
to see the conflicted file. - Open the file in a text editor.
- Use
Understand the Changes:
- Examine the conflict markers («««<, =======, »»»>).
- Determine the changes made in each branch.
Resolve the Conflict:
- Manually edit the file, keeping the necessary changes from both branches.
- For example, you might keep the performance optimizations from
performance-optimization
while incorporating the new checkout logic fromfeature-checkout
.
Stage and Commit:
- Stage the resolved file:
git add checkout.js
- Commit the changes:
git commit -m "Resolved merge conflict in checkout.js"
- Stage the resolved file:
Additional Considerations
- Testing: Thoroughly test the merged code to ensure it works as expected.
- Git Blame: Use
git blame
to see who made specific changes in case you need to revert or understand the reasoning behind them. - Interactive Rebase: If you’re working on your own branch and prefer a linear history, you could use an interactive rebase to replay your commits on top of the other branch, resolving conflicts as you go. However, be cautious with interactive rebasing on shared branches.
Let’s Discuss Interactive Rebase
How it Works
Start the Interactive Rebase:
git rebase -i <base-commit>
Replace
<base-commit>
with the desired starting point for the rebase.Edit the Rebase Script:
- A text editor will open with a list of your commits.
- Each line represents a commit, with commands you can modify.
Modify Commits:
- You can:
- Reorder commits by changing their position in the list.
- Combine commits by squashing them into a single commit.
- Edit a commit’s message by changing the command to
reword
. - Squash commits into the previous commit by changing the command to
squash
. - Drop a commit by deleting the line.
- You can:
Save and Exit:
- Save the changes and exit the editor. Git will apply the rebase.
Example
You have a branch with several commits, and you want to clean it up before merging:
commit1: Initial commit
commit2: Added feature A
commit3: Fixed bug
commit4: Improved feature A
commit5: Added feature B
You decide to squash commits 2 and 4 into a single commit, and edit the message of commit 3:
pick commit1: Initial commit
squash commit2: Added feature A
reword commit3: Fixed bug
squash commit4: Improved feature A
pick commit5: Added feature B
After saving, Git will create a new commit combining commits 2 and 4, and you’ll be able to edit the message of commit 3.
Cautions
- Interactive rebase is powerful but risky. Once you start a rebase, you can’t easily undo it.
- Avoid interactive rebase on public branches. It can cause conflicts for other developers.
- Test thoroughly after rebase.
Using Interactive Rebase to Resolve Conflicts
Scenario
You’re working on a feature branch called feature-checkout
and encounter a merge conflict with the main
branch. You decide to use interactive rebase to resolve the conflict and clean up your commit history.
Steps
Create a new branch:
git checkout -b temp-branch
This is a safety measure to protect your original branch.
Start the interactive rebase:
git rebase -i main
This will open a text editor with a list of your commits.
Identify the conflicting commit: Look for the commit that introduced the conflict.
Edit the rebase script:
- Change the line for the conflicting commit to
edit
. - Save and exit the editor.
- Change the line for the conflicting commit to
Resolve the conflict:
git checkout main
Merge the necessary changes from
main
into your current commit.git checkout temp-branch
Continue the rebase:
git rebase --continue
Complete the rebase: Continue resolving conflicts or editing commits as needed. Use
git rebase --continue
orgit rebase --skip
to proceed.Merge the rebased branch:
git checkout main git merge temp-branch
Additional Tips
- Use descriptive commit messages: This helps in understanding the changes when reviewing the rebase script.
- Test thoroughly: After resolving conflicts and completing the rebase, make sure your code works as expected.
- Consider using a visual merge tool: This can make it easier to see the differences between the two versions of the code.