Xar
Xar

Reputation: 7940

Git: conflicts when pulling latest version of the current branch

I'm afraid this must have been asked thousands of times but I haven't found it anywhere and it happens to me all the time.

At work, when I try to pull the latest commits on the branch that I currently have checked out, I get conflict errors. This happens to me even if I haven't made any changes to the branch.

So I end up deleting my local branch and fetching the branch as if it were brand new.

This is an example of what I'm describing:

➜  my_project git:(foo_branch) ✗ git pull
From github.com:Blabla/my_project
 + c6a6ccf...39052a7 foo_branch -> origin/foo_branch  (forced update)
 + aa9d3a9...22cdf8c development                    -> origin/development  (forced update)
 * [new branch]      docs/DE3802                    -> origin/docs/DE3802
   0b2ed86..1256c5c  docs/US16862                   -> origin/docs/US16862
   15a755b..8a3ebdd  master                         -> origin/master
Auto-merging cloudformation/appsync/schema.graphql
CONFLICT (add/add): Merge conflict in my_project/media/clients/foo/file_1
Auto-merging my_project/media/clients/foo/policy_coverages
Auto-merging my_project/apps/engine/tests/test_functions.py
CONFLICT (content): Merge conflict in my_project/apps/engine/tests/test_functions.py
Auto-merging my_project/apps/engine/functions.py
CONFLICT (content): Merge conflict in my_project/apps/engine/functions.py
Auto-merging my_project/apps/engine/defaults.py
CONFLICT (content): Merge conflict in my_project/apps/engine/defaults.py
Automatic merge failed; fix conflicts and then commit the result.

How can I just pull the latest changes in the branch and make them overwrite the older version I have?

Upvotes: 1

Views: 1450

Answers (1)

Schwern
Schwern

Reputation: 165218

Someone has been altering history and force pushing their branches.

tl;dr: Don't make local branches unless you need to make changes. Instead of creating and checking out foo_branch, checkout origin/foo_branch.

tl;dr: Use git pull --rebase to replay your local changes on top of the remote branch. I recommend rebasing all pulls. It is safe. You can do so by adding this to your .gitconfig.

[pull]
        rebase = merges

A git pull is a git fetch plus a git merge. The first portion of the git pull is a record of the git fetch portion to update your remote tracking branches.

From github.com:Blabla/my_project
 + c6a6ccf...39052a7 foo_branch -> origin/foo_branch  (forced update)
 + aa9d3a9...22cdf8c development                    -> origin/development  (forced update)
 * [new branch]      docs/DE3802                    -> origin/docs/DE3802
   0b2ed86..1256c5c  docs/US16862                   -> origin/docs/US16862
   15a755b..8a3ebdd  master                         -> origin/master

Let's focus on just foo_branch.

 + c6a6ccf...39052a7 foo_branch -> origin/foo_branch  (forced update)

This says that the remote branch foo_branch is being tracked locally in origin/foo_branch. You had origin/foo_branch at c6a6ccf. The fetch moved it to 39052a7. +, (forced_update) and ... indicate it was not a "fast-forward".


A "fast-forward" is when a branch can be updated with no merge. For example...

A - B - C [master]
         \
          D - E [feature]

If we git merge feature into master there is no need for a merge, Git will simply move the master label to the same commit as feature.

$ git merge feature

# Fast forward master from C to E.
A - B - C - D - E [master]
                  [feature]

OTOH if master had changes, a merge would be necessary to combine both the changes in feature with the new changes in master.

A - B - C - F [master]
         \
          D - E [feature]

$ git merge feature

A - B - C - F - G [master]
         \     /
          D - E [feature]


Normally, git fetch is a fast forward. origin/foo_branch is a snapshot of your last git fetch. You should have no additional changes to origin/foo_branch so any new changes can be fast forwarded. Here's what that might look like. Remember, a git pull origin foo_branch is a git fetch origin and git merge foo_branch origin/foo_branch.

origin
A - B - C - D - E [foo_branch]

local
A - B - C [foo_branch]
          [origin/foo_branch]

$ git fetch origin

local
A - B - C [foo_branch]
         \
          D - E [origin/foo_branch]

$ git merge foo_branch origin/foo_branch

# Fast-forward foo_branch from C to E.
A - B - C - D - E [origin/foo_branch]

But what happens if someone rebases foo_branch and does a force push?

origin
A - B1 - C1 - D - E [foo_branch]

local
A - B - C [foo_branch]
          [origin/foo_branch]

$ git fetch origin

local
A - B1 - C1 - D - E [origin/foo_branch]
 \
  B - C [foo_branch]

B1 and C1 represent rebased commits. After the fetch foo_branch and origin/foo_branch have "diverged". The next part of git pull cannot fast-forward and requires a merge.

$ git merge foo_branch origin/foo_branch

local
A - B1 - C1 - D - E [origin/foo_branch]
 \                 \
  B - C ------------ M [foo_branch]

Even though there were no local changes to foo_branch, there are likely to be conflicts between B and B1 and C and C1.


So, what to do about it.

First, don't make local branches if you don't need to make local changes. Instead of making a local foo_branch, reference origin/foo_branch. Then there is no local branch to fall out of sync.

Second, change git pull so it does a rebase instead of a merge. You can do this individually with git pull --rebase and globally by setting pull.rebase to merges.

Going back to our example above where foo_branch and origin/foo_branch have diverged after foo_branch was remotely rebased...

origin
A - B1 - C1 - D - E [foo_branch]

local
A - B - C [foo_branch]
          [origin/foo_branch]

git pull --rebase will not attempt a merge. It will instead rebase your local changes to foo_branch on top of the new origin/foo_branch. There are no local changes, so foo_branch is simply brought up to date with the new origin/foo_branch.

local
A - B1 - C1 - D - E [foo_branch]
                    [origin/foo_branch]

If you did have changes, they would be replayed on top of the new origin/foo_branch.

origin
A - B1 - C1 - D - E [foo_branch]

local
A - B - C [origin/foo_branch]
         \          
          F - G [foo_branch]

$ git pull --rebase

local
A - B1 - C1 - D - E [origin/foo_branch]
                   \          
                    F - G [foo_branch]

Third, discuss procedures around forced pushes with other members of your project.

Upvotes: 3

Related Questions