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

  1. 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.
  2. 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.
  3. Git Mergetool:

    • A built-in Git command that opens a merge tool for the current conflict.
  4. 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

  1. Identify the Conflict:

    • Use git status to see the conflicted file.
    • Open the file in a text editor.
  2. Understand the Changes:

    • Examine the conflict markers («««<, =======, »»»>).
    • Determine the changes made in each branch.
  3. 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 from feature-checkout.
  4. Stage and Commit:

    • Stage the resolved file:
      git add checkout.js
      
    • Commit the changes:
      git commit -m "Resolved merge conflict in checkout.js"
      

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

  1. Start the Interactive Rebase:

    git rebase -i <base-commit>
    

    Replace <base-commit> with the desired starting point for the rebase.

  2. 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.
  3. 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.
  4. 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

  1. Create a new branch:

    git checkout -b temp-branch
    

    This is a safety measure to protect your original branch.

  2. Start the interactive rebase:

    git rebase -i main
    

    This will open a text editor with a list of your commits.

  3. Identify the conflicting commit: Look for the commit that introduced the conflict.

  4. Edit the rebase script:

    • Change the line for the conflicting commit to edit.
    • Save and exit the editor.
  5. 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
    
  6. Complete the rebase: Continue resolving conflicts or editing commits as needed. Use git rebase --continue or git rebase --skip to proceed.

  7. 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.