I would like to find an answer to why git behaves differently when I try to merge my local changes with those of master, depending on the commands and sequence used.
To ensure that I start from a clean environment, I reset everything to the latest local commit 69cf18c...
and then I cherry-pick my own changes (just one file) stored already in a Gerrit server:
git reset --hard 69cf18c...
git fetch ssh://... && git cherry-pick FETCH_HEAD
From here I have tried 3 ways to solve it and only the third one works well:
Option 1: Pull with rebase
git pull --rebase
Git will show me hundreds of changes that have nothing to do with the file I cherry-picked. Even if I try git rebase --skip
another mysterious conflict will come, again and again, indefinitely. Why is this happening? Is my local history polluted with some older local commits that distort the rebase?
Option 2: Pull without rebase
git pull
Git will show me only one conflict, the one which has to do with my file, up to here everything ok. However after solving the conflict and running git add -u && git commit
, git creates a new commit on top of mine which I cannot merge together with mine running git rebase --interactive HEAD~2
(then I would pick and squash respectively):
git log
commit f65738375
Merge: 6c4e66a f235f75
...
commit 6c4e66a88
...
Git does not list it in the editor the commit "Merge", but instead hundreds of other commits. Why cannot I merge the conflict into my commit?
Option 3: Reset, pull and then cherry-pick
If I cherry-pick after pulling, everything works fine:
git reset --hard 69cf18c...
git pull
git fetch ssh://... && git cherry-pick FETCH_HEAD
# solve the conflict
git add -u && git commit -c FETCH_HEAD
Why in this order everything works smoothly?
Is there an explanation for these 3 options? Thank you in advance!
First you need understand difference between "git rebase" and "git merge".
"git pull --rebase" is same as "git fetch origin;git rebase"
"git pull" is same as "git fetch origin;git merge"
Git rebase moves all your local commits up to top of remote branch (by default is master branch of the remote). If multiple commits on remote branch have conflict with your local commits, you have to resolve one by one, not once, just like you climb up stairs. Finally after rebase, you'll find your local commits are on top of remote commits, and all your local commit id are changed.
Git merge just connect your local branch and remote branch with one additional merge commit without changing either commit on local or remote branch. It asks for conflict merge once, such conflict resolving information is saved in the new created merge commit.
Git rebase is better for resolving local changes before commit. Git merge is better for merge remote branches on server, or git pull request, which won't change commit id there. Gerrit doesn't use pull request as github or bitbucket. The best practice for Gerrit is to avoid "git merge" or "git pull" locally.
It looks you hadn't rebase your local branch for a long time so there are many commits on remote branch conflicts with yours. So direct rebase is frustrated as option 1.
You can do this to resolve conflict.
git branch -b WIP
git branch -D master
git checkout -b master origin/master
git checkout master
git cherry-pick xxx (xxx is local commit on WIP branch)
...
git cherry-pick xxx
git fetch ssh://... && git cherry-pick FETCH_HEAD
In this way, you do not have any "git merge" operation, and save time to avoid rebase for mutliple conflicts.