Git is a content-addressable database, meaning every object is stored under the hash of its contents. Git also does 3-way merges. It finds the 'merge-base' (there's even a command in git - merge-base
- git help merge-base
to read up on it) that finds the most recent ancestor that they have in common, i.e. the point that both forked off of at some point in the past.
Unlike 3-way merges in non-hashed systems, which need to diff files for comparison, git can look at the 3 hashes. Let's say you're merging feature into master. If the 3 hashes of a particular path to a file in the project are identical, it skips it - it hasn't changed in either branch - this is super fast. If the hash has changed in master but not in feature, then it just uses the master version, because someone changed it on master, but no one cared to on feature, so the master version is the changed/important one. Vice-versa, if it changed in feature, but not in master, it just uses the feature version. This is also super fast. In fact, this is the majority case most of the time. Most files don't change in larger projects, so a merge only ends up comparing some small set of files for which the hash has changed in both branches.
If the hash has changed in both branches from what it was at the merge-base of those two branches, then git falls back to merging the old-fashioned way, comparing things line-by-line. If one set of lines changed in one path, but not in the other (as compared to the merge-base copy), then it merges the changes from that branch. If you changed a variable name in both branches, then it would wrap it in conflict markers and tell you you need to resolve this manually, which is the only right way to do this. As Linus Torvalds said, you don't want a machine trying to figure that out for you, and he's right.