smwikipedia
smwikipedia

Reputation: 64205

Why git pull offers a chance to merge but git push doesn't?

(What I described below are all happening in a local git repo for experiment. No remote repo involved.)

I create 2 local branches lb1 and lb2. They start from the same commit node.

git branch lb1
git branch lb2

I make them upstream of each other. As below:

$ git checkout lb2
Switched to branch 'lb2'

$ git branch -u lb1
Branch lb2 set up to track local branch lb1.

$ git checkout lb1
Switched to branch 'lb1'

$ git branch -u lb2
Branch lb1 set up to track local branch lb2.

Then I make them diverge by making different changes on each. As shown below:

enter image description here

$ git branch -vv
* lb1    6774aa6 [lb2: ahead 1, behind 1] lb1 change
  lb2    cdd1247 [lb1: ahead 1, behind 1] lb2 change

I am currently on lb1. Then I tried to push one branch (lb1) to the other (lb2) to see what will happen:

$ git push . lb1:lb2
To .
 ! [rejected]        lb1 -> lb2 (non-fast-forward)
error: failed to push some refs to '.'
hint: Updates were rejected because a pushed branch tip is behind its remote
hint: counterpart. Check out this branch and integrate the remote changes
hint: (e.g. 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.

So if I understand it right, it says the pushed branch tip (lb1) is behind its remote counterpart (lb2). I have to first checkout lb1 and pull from lb2 to integrate lb2's change.

So I do this: (note that I am on lb1 branch now)

$ git pull
From .
 * branch            lb2        -> FETCH_HEAD
Auto-merging file1.txt
CONFLICT (content): Merge conflict in file1.txt
Automatic merge failed; fix conflicts and then commit the result.

So git pull gives me a chance to merge.

After I merge, I can push lb1 to lb2:

$ git push . lb1:lb2
Total 0 (delta 0), reused 0 (delta 0)
To .
   cdd1247..2dfb555  lb1 -> lb2

And revision graph is like this:

enter image description here

And the lb2's change is added into lb1's log history:

enter image description here

To summarize:

My question is:

Why git push doesn't offer a chance to merge? From the perspective of English language, I think push and pull are symmetric. So I expect same result from them.

Upvotes: 1

Views: 703

Answers (3)

Bruno Bronosky
Bruno Bronosky

Reputation: 70339

Merging on pull means that your local branch keeps its history in tact and additional commits are stacked on top. You have the result local and can test it before anyone else is affected. If you don’t like the result, pop the committees of the stack.

If a merge happened on a push, the remote would have a result that has never been seen before. Someone would then have to retrieve it to even know what the state was. Then it would have to be tested. All while the rest of the team are exposed it to. This is just a terrifying scenario. It only makes sense in a situation where repo is the sole concern of an individual and there is no consequential CD in place.

Upvotes: 0

Roger Gee
Roger Gee

Reputation: 871

I think @dunni hit the nail on the head (i.e. merged the nail into the board) in the comments when they mentioned that a git pull is actually a convenience notation for two distinct operations. What I would challenge from your question's assertions is that git-pull is symmetric to git-push. It may be more accurate to state that a git-fetch is symmetric to a git-push. Of course, as you said, in English push and pull are very much indeed opposites to each other; however that doesn't necessarily imply symmetry in their usage in other domains.

As to why the implementors of git decided to have pull attempt an automerge, I can only speculate. My theory is that because the working repo has such a concept as tracking branches it makes sense to automatically merge the remote tracking branch into the local branch to perform the pull operation. This is because you will have to do the merge/fast-forward necessarily to use the updates in a non-detached HEAD state. The repository has to keep this separation in order to know what commits the remote has versus what it has locally. However a remote, bare repository has no such concept as tracking branches: it just updates its refs when it runs git-receive-pack on the backend. It doesn't care what commits are referenced; that's why force push (git push -f) exists. In fact, force push is not even a construct in the git-pack-protocol but just a client-side option to ignore the normal checks for keeping the history intact. When you push, you are not implying the need for a merge/fast-forward: you're only implying an update of the object database on the remote and its list of refs. On the client end, the remote tracking branch is simply updated to match what you successfully pushed (whether forced or not).

To summarize: a fetch necessitates a merge because the fetched remote tracking branch has to be merged into a corresponding local branch for you to use in your working repository. However with push you may not necessarily have a need to merge; you are just updating the remote branches.

Upvotes: 4

max630
max630

Reputation: 9238

You probably should better ask it in the git maiilist, but there is important difference: the merge performed by pull is yet part of your personal history. You can test it, or update merge message, or change your mind and reset to commit before the merge. You can spend time resolving conflicts.

If git push were merging, this automatic merged commit would immediately become part of public history. It would also cause synchronisation issues, in case somebody pushes at the same moment, and merge requires resolving conflicts or writing commit message.

Actually, I have a script which tries to implement it, but I cannot say I've seen much feedback about it. These days people are used to work with mostly personal feature branches, and merge only done when server is merging pull request.

Upvotes: 1

Related Questions