Git merge bug is a Git merge bug. Git merge bug is a Git merge bug. Maybe your expectations are just wrong. This article introduces how to do a Git merge, step by step, through the introduction of three-way merge and Git merge strategy, let everyone have an accurate expectation of Git merge results, and avoid merge accidents.

This is a series of articles, planned to include three:

  • This is the real Git — Git internals
  • This is the real Git — branch merge
  • This is the Real Git — Git Practical Tips (not yet completed)

Story time

Before we get into the main body, listen to this story.

As shown in the figure below, Xiaoming pulled A dev branch from node A, added A new file http.js to node B, and merged it into the master branch, and merged node E. Delete the merge and add a new REVERT node E’. A few days later, Xiaoming continued to develop a new file on the dev branch, main.js, and imported the logic in the http.js file. Everything worked well on the dev branch. When he merged the dev branch into the master, he found that the http.js file was missing, causing the logic in main.js to run incorrectly. But there is no conflict in this merger. He had to do it again and wondered if it was a Git bug.

Two oft-heard phrases:

— “Files that were there before the merge are gone after the merge”

— “I have a Git bug”

Git merge: Git merge: Git merge: Git merge: Git merge I suspected it was a Git bug. The purpose of this article is to explain how Git merges branches, and some of the underlying concepts, so as to avoid the problems described in this story, and to have an accurate expectation of the results of Git merges.

How do I merge two files

Before we look at merging two branches, let’s look at merging two files, because merging two files is the basis for merging two branches.

You’ve all heard of the term “three-way merge”. Have you ever wondered why a merge of two files requires a three-way merge, and whether only two files can be merged automatically? The following figure

The obvious answer is no. As in the example above, Git has no way of determining whether this line of code was modified by me or the other party, or whether it didn’t exist before and was added by both of us. Git can’t do automatic merge for us at this point.

Git merges two files as Print(“hello”). Git merges two files as Print(“hello”). Git merges two files as Print(“hello”).

So what is conflict? A conflict is simply a three-way merge in which all three parties are different, i.e., merge base, where our branch and someone else’s branch make changes to the same place.

Git merge strategy

Git merge Merge git merge Merge git merge merge git merge merge git merge merge git merge As shown above, merging the master branch into the feature branch adds a COMMIT node to record the merge.

Git has many merge policies. The most common ones are fast-forward, Recursive, Ours, Theirs, and Octopus. The following describes the principles and application scenarios of different merge policies. Git merges -s < policy name > Git merges -s < policy name >

Knowing how Git merge strategies work will give you an accurate idea of what Git merge results will be.

Fast-forward

Fast-forward is the simplest merge strategy. As shown in the figure above, merge some feature branches into the master branch. Git only needs to move the master branch to the last commit node.

Git merge –no-ff Git merge — Git merge –no-ff Git merge — Git merge –no-ff Git merge

Recursive

Recursive is the most important and common Git branch merge strategy, and is the default behavior when merging two branches that have forks. The algorithm can be simply described as: recursively search for the only common ancestor node with the shortest path, and then use it as the base node for recursive three-way merge. It’s a little convoluted, but let’s explain it by example.

In a simple case like the following, where the letters in the circles are the contents of the current commit file, when we merge the middle two nodes, find their common ancestor (first on the left), and then perform a three-way merge to get the result B. (Because the merged base is “A”, the lower branch is still “A” without modification, and the upper branch is changed to “B”, so the merged result is “B”).

But the reality is always more complicated, with overlapping chains of historical records, as shown below

When Git is looking for the shortest common ancestor node, it can find two nodes. If Git selects the following node, then Git will not be able to merge automatically. Because according to the three-way merge, there is a conflict here that needs to be resolved manually. (Base is “A”, the merged two branches are “C” and “B”)

If Git chooses the node shown below as the base of the merge, Git can automatically merge the result “C” directly according to the three-way merge. (Base is “B”, the merged two branches are “C” and “B”)

As human beings, we can naturally see that the result of the merge should be “C” in this example (as shown below, nodes 4 and 5 are already “B”, node 6 is changed to “C”, so the expected merge is “C”).

How do you ensure that Git can find the right merged base node to minimize conflicts? The answer is that when Git is looking for the common ancestor node with the shortest path, if the ancestor node that meets the condition is not unique, then Git will continue to recursively look for the ancestor node until it is unique. I’m going to use this example again.

As shown in the figure below, we want to merge nodes 5 and 6, and Git finds the ancestors of nodes 2 and 3 with the shortest path.

Since the common ancestor node is not unique, the Git recursion takes node 2 and node 3 as the nodes we want to merge, search for their common ancestor with the shortest path, and find the only node 1.

Then Git uses node 1 as the base to merge node 2 and node 3 in three ways to get a temporary node. According to the result of the three-way merge, the content of this node is “B”.

Then take this temporary node as the base, three-way merge of node 5 and node 6 to get the merged node 7. According to the result of three-way merge, the content of node 7 is “C”.

At this point Git completes the recursive merge and automatically merges nodes 5 and 6, resulting in “C” with no conflicts.

Recursive strategy has been a lot of scenes prove that it is a to minimize conflict merge strategy, we can see the funny thing is, for the two merging branches of the intermediate node node 4, 5 (pictured above), only to be involved in the calculation of base, and the final real three-way merge to merge nodes, only including the terminal and the base node.

Note that Git only uses these policies to minimize conflicts. If a conflict is unavoidable, Git will prompt you for a conflict that needs to be resolved manually. (I.e., conflict in the real sense).

Ours & Theirs

Ours and Theirs merge strategies are also relatively simple. To put it simply, the historical records of both parties are kept, but the file changes of this party are completely ignored. Git merge -s ours dev = git merge -s ours dev = git merge -s ours dev = git merge -s ours dev

However, if the theirs is used, the file content of the current branch is completely discarded and the file content of the other branch is directly adopted.

One of the scenarios where these two strategies are used is if you want to implement the same feature, you try both solutions at the same time, one on the branch dev1 and the other on dev2, and after testing you choose dev2. Git merge -s ours dev1 if you don’t want to throw away dev1’s attempt to merge it into the main trunk for later review, you can run git merge -s ours dev1 in the dev2 branch.

Octopus

Git merges two or more branches. Git merges two or more branches. Git merges two or more branches. For example, git merge dev2 dev3 on the dev1 branch.

One of his scenarios is in a test or pre-release environment, where you need to merge changes from multiple development branches together. Without this strategy, you can only merge one branch at a time, resulting in a large number of merge nodes. Using a merge strategy like Octopus, you can merge them all in with a single merge node.

Git rebase

Git rebase is also a common merge method. Git rebase differs from Git merge in that it changes the commit node corresponding to the change history.

As shown in the figure below, when rebase master is performed in the feature branch, Git will start from the commit node corresponding to the master branch and add two new commit nodes to replace the commit nodes in the feature branch. The reason is that the parent pointing to the new commit changes, so the corresponding SHA1 value will also change, so the commit in the original feature branch cannot be reused. (This sentence requires basic knowledge of the passage to understand)

Git merge versus Git rebase. My personal opinion is that there is no silver bullet. You can choose according to your team and project habits. Git rebase allows you to keep a clear history, while Git merge allows you to keep the true commit time and other information, and is not prone to problems and conflicts. The only caveat is that you do not rebase a multi-user branch that is already remote.

One of my personal habits is to use Rebase for local branches or remote branches that are sure to be used by only one person, and merge for the rest.

Git rebase -i also has a very useful interactive mode called Git rebase -i. You can compress several Commits, modify commit information, and discard a commit. For example, if I want to compress 260a12a5 and 956e1d18 and merge them with 9DAE0027 as a commit, I simply change the pick in front of 260a12a5 and 956e1d18 to “S” and save.

Git rebase – I has a lot of useful functions that are not available in git rebase.

conclusion

Now let’s look at the example at the beginning of this article to see why the last merge resulted in the http.js file disappearing. According to Git’s merge policy, Git defaults to Recursive when merging two branches that fork (D, E ‘). Find the common ancestor node B of the shortest path of D and E ‘, take B as base, and do three-way merge of D and E ‘. B has http.js, D has http.js and main.js, and E ‘has nothing. According to the three-way merge, there are http.js in B and D and there is no change, E ‘deletes http.js, so the merge result is no http.js, no conflict, so the http.js file is missing.

There are several ways to revert to this example: 1. After deleting the dev branch (E’), you can remove it from the dev branch (E’) and continue to work on it. 2. When node D is merged back into node E ‘, you can revert from node E ‘to’ REVERT ‘and then merge node D.

Git has many branch merge strategies. This article introduces fast-forward, Recursive, Ours/Theirs, Octopus and three-way merge strategies. Knowing these merge strategies and their usage scenarios will help you avoid merge problems and have an accurate forecast of the results.

I hope this article is useful to all of you. Interested students can browse my blog www.lzane.com or check out my other articles.

reference

  • Three-way merge blog.plasticscm.com/2016/02/thr…
  • www.youtube.com/watch?v=Lg1…
  • Books Scott Chacon, Ben Straub – Pro Git-APress (2014)
  • Books Jon Loeliger, Matthew McCullough – Version Control with Git, 2nd Edition – O ‘Reilly Media (2012)