Your expectation is wrong. You will need to adjust your expectation of what Git will do.
Merge is, fundamentally, about combining changes.
Meanwhile, commits store snapshots, not changes. That is, each commit has a complete snapshot of every file (that Git knew about, at the time you, or whoever, made that snapshot).
This leaves us with a dilemma: how can we combine changes, when we don't even have any changes?
You might object, because whenever you look at a commit—with, e.g., git log -p, or git show raw-commit-hash-ID—you see changes. To understand how this works, let's take a closer look at a commit. (Don't worry that this is long! We will eventually get to git merge.)
What is a commit?
Above, I said that a commit stores a snapshot. This is true, but it's not complete. It's like saying that a human has a head. Sure, all humans—well, all live ones—have heads. But a head alone does not make a human, even if that might be the most important part for some particular purpose, such as reading a StackOverflow Q&A.
The first thing to know about a commit is that it's numbered. These commit numbers are not simple counting numbers—we don't have commit #1, followed by #2, then #3, and so on. Instead, each commit has a random-looking, and very large, number, that gets represented as a big hexadecimal value such as a123456, which might be followed by 7f31242 or something. (Compare this to, e.g., SVN, which does have simple sequential numbers: rev 12346 comes right after rev 12345.)
The commit number of any commit is unique across every Git repository everywhere. That's why these numbers have to be so big and ugly. The number is actually a cryptographic checksum of the commit's content, which means that neither the number nor the content can ever change, and all Gits everywhere will compute the same number for that particular content. This is how two Gits can know whether they share some commit (or in fact any internal Git object, not just a commit). One Git hands the other Git a number, for a commit the one Git has. The other Git looks in its database: does it have that number in it? If so, it has that commit. If not, it gets that commit—all of its content, that is—from the other Git, and now they both have that commit.
So that's how Git finds a commit: by its number. But that's still not what it is, so let's look a bit closer. Each commit consists of two parts:
There's that snapshot we've mentioned several times. (This is actually implemented indirectly, as a tree object, though you don't normally need to care about this.) Here Git has, saved forever, a full copy of every file that Git knew about at the time the commit got made. The files inside the commit are not ordinary files, though: they're in a special, Git-only format, compressed and de-duplicated. (This is implemented through yet more of these internal objects, specifically blob objects, but again you don't need to know this.)
Besides the required snapshot, each commit has some metadata, or information about the commit itself: who made it, when, and why (their log message), for instance. (Technically, one metadata line gives the hash ID of the snapshot, but yet again, you don't need to know this.)
So a commit consists of this metadata-plus-snapshot. That's what a commit is: a numbered entity, holding some metadata and a snapshot. The metadata includes your name, if you made the commit. But it also includes something purely for Git itself. Each commit holds, in its metadata, the commit numbers—the hash IDs—of some set of previous commits.
Most commits are "ordinary commits". What makes a commit ordinary is that it has, as its previous-commit hash ID, just one hash ID. This hash ID is that of the immediately previous commit. We call this one immediately-previous commit the parent of the commit. These hash IDs allow Git to access a whole chain of commits just by knowing the hash ID of the last commit in the chain.
We can draw this situation like this:
... <-F <-G <-H
Here, the letters stand in for real commit hash IDs, which are too big and ugly to bother with. Of course we'll run out of letters quickly (which is one reason Git uses those big ugly hash IDs), but as long as we only draw a few at a time, they'll do. Older commits are on the left and newer ones are on the right, here.
Somehow, we tell Git the hash ID of the last commit, H, in this chain. Git can use this to read out the data-and-metadata, including the full snapshot of every file. So that allows Git to check out the commit, extracting all the files to our work area. But that also gives Git the raw hash ID of earlier commit G.
This means that instead of just extracting the snapshot for H, Git can now use the hash ID of G to extract the snapshot of G. Then Git can compare the snapshots. The special format of a snapshot makes this a bit easier, with the de-duplication: Git can skip right over (de)duplicated files and only bother comparing files that are actually different in the two commits.
By comparing two different files stored under the same name, Git can then figure out which lines changed in those two versions of that file. That's what git log -p or git show shows us. Git didn't store the changes—it stored two snapshots—but comparing the first snapshot to the second tells Git what changed.
Of course, now that Git has the hash ID of commit G, it can extract G's metadata, which gives Git the hash ID of still-earlier commit F. Git can go through the exact same process as last time, and show us what changed. From F, Git can move back one more step, to whatever commit comes before F, and so on. This process can go on forever, or rather, until Git comes across the very first commit. That first commit doesn't have a previous (parent) commit. That lets git log stop going backwards.
There's one big hitch here. We said somehow, we tell Git the hash ID of the last commit. But how? Did we write down that hash ID on paper, or a whiteboard, or what? Maybe we saved it in a file, and listed out the contents of the file and used cut-and-paste with the mouse. (This method does work, by the way, at least for a while.) But these are all silly methods. We have a computer. Why not have it save the hash ID somewhere?
Branch names
This is where branch names come in. A branch name like main or develop or my or whatever you like simply stores the hash ID of one commit, which we—or Git—will then believe is the last commit in that chain of commits. We can draw our current situation like this, then:
...--F--G--H <-- main
if it's the branch name main that lets Git find commit H.
Once we have one branch name, though, we're tempted to add more. Let's make a second branch name that also points to commit H, like this:
...--G--H <-- branch1, main
These two branch names allow us to pick out one commit. Both names pick out commit H right now, so in a sense, it doesn't matter which name we use; but if all branch names always had to pick out the same commit, that would be a problem. So that's not the case, and when the branch names start to move around, we'll need a way to remember which name we are using.
To handle this, let's attach a special name, HEAD, written in all uppercase exactly like this, to exactly one branch name at a time. Whatever name HEAD is attached to, we'll say that this is our current branch. Whatever commit that name points to, we'll say that this is our current commit.
We do this attaching with git checkout or, in Git 2.23 or later, git switch. For instance, after git switch main we have:
...--G--H <-- branch1, main (HEAD)
but after git switch branch1 we have:
...--G--H <-- branch1 (HEAD), main
Either way, we'll be on commit H, but after the second switch, we're using the name branch1 to get there. (Note: The switch command subsumes the "safe" part of git checkout; the "unsafe" part of git checkout is now in git restore. The old git checkout command, which has both the "safe" and "unsafe" operating modes, still exists. If your Git predates Git 2.23, you must use git checkout here, and hope it doesn't accidentally use the unsafe subset.)
Now that we're on branch1, let's make a new commit. (We won't worry about the exact mechanism; let's just assume that you know what git commit does. It's complicated if we get into every detail, but you've been doing it OK so far, no doubt, so you know enough here.) We'll fuss with files and git add and git commit, which will build a new snapshot I with appropriate metadata. New commit I will point back to existing commit H. As the last step of git commit, Git will write I's hash ID into the current branch name, so that we have this now:
I <-- branch1 (HEAD)
/
...--G--H <-- main
If we make another new commit, which we will call commit J, we will have:
I--J <-- branch1 (HEAD)
/
...--G--H <-- main
Now let's make a new branch name, using commit H—not J—as its initial stored hash ID. We'll also get "on" this branch using git checkout or git switch. Perhaps we run git switch -c branch2 main, for instance. This gives us:
I--J <-- branch1
/
...--G--H <-- branch2 (HEAD), main
Note how commit H is now the current commit, with branch2 the current branch. We now have, in our work area, the files from commit H, not the files from commit J.
Let's make two new commits now on branch2:
I--J <-- branch1
/
...--G--H <-- main
\
K--L <-- branch2 (HEAD)
Note that commits up through and including H are on all three branches, while commits I-J are only on branch1, and K-L are only on branch2. Commit H is the last commit on main; commit J is the last commit on branch1; and commit L is the last commit on branch2.
The purpose of the names is to locate these commits, and allow us to git checkout or git switch to those names so that as we make new commits, those names get updated. What Git mainly stores—and exchanges with other Git repositories—is the commits themselves, though. Any other Git repository has its own branch names. They might use the same names we use, or not; if they do use the same names, they might pick out the same commits, or not.
A high-speed look at git pull
Before you merged, you ran:
git checkout M
git pull origin M
The checkout picked out your branch name M, attached your Git's HEAD there, and got you the last commit on that branch, via the hash ID recorded under that name. The git pull then does something a bit complicated: it runs two commands. The first one is git fetch—in this case, git fetch origin M—and the second one is configurable, but defaults to git merge.
The git fetch operation calls up some other Git, connecting that other Git to some other Git repository, based on the URL stored under the name origin. They check their branch M, in this case, to see what their last commit is. They hand your Git that commit's hash ID. If your Git doesn't have this commit yet, your Git gets this commit, and any earlier commits as well that they have that you don't. So now you have all the commits you both had earlier, plus any commits they had that you didn't.
This git fetch operation updates a non-branch name that your Git uses to remember the commit hash ID stored in their branch name M. The non-branch name that your Git uses is origin/M. (If your Git is truly ancient—older than 1.8.4—this kind of git fetch fails to update your origin/M; the rest still works, though, through a mechanism I won't describe. If your Git is newer, technically your Git still uses the old mechanism. It also updates origin/M, though, which makes the whole thing easier to think about.)
The second command now uses the updated hash ID—in origin/M in this case—to incorporate the new commits into your current branch, which is of course M due to the earlier git checkout. As long as you didn't make your own commits here, this winds up doing what Git calls a fast-forward merge. This thing is kind of misnamed: a fast-forward is not a merge at all. It just marches your own branch name forward, so that your M and their M (now in your origin/M) will end up naming the same commit.
I'm going to stop using the name M as a branch name here, since I use these single uppercase letter things for commit names. Let's call this branch develop instead. We'll split up the git pull into its two separate components, so that we know exactly which commands really got used (with git pull we don't necessarily know which command ran after git fetch):
git checkout develop
git fetch
git merge origin/develop
We'll say that we also made our own branch, not named develop but rather branch1, as before. When we started, we had:
...--G--H <-- develop (HEAD)
and we added our branch name and then our I-J commits:
I--J <-- branch1 (HEAD)
/
...--G--H <-- develop
Now we've run git checkout develop and then git fetch, which got us:
I--J <-- branch1
/
...--G--H <-- develop (HEAD)
\
K--L <-- origin/develop
because they made two commits, K-L, on their develop, and we picked them up.
We now run (via git pull) git merge origin/develop. This does a fast-forward instead of a merge, which gives us this as the result:
I--J <-- branch1
/
...--G--H
\
K--L <-- develop (HEAD), origin/develop
Note how no commits have changed—they can't; it's literally impossible—but our branch name develop now selects commit L. That's what the fast-forward did: it moved our branch name.
We're now ready to look at a true merge.
Merge (true merges, not fast-forwards)
We now switch back to our branch, branch1 (the one you were calling My):
I--J <-- branch1 (HEAD)
/
...--G--H
\
K--L <-- develop, origin/develop
and now we run git merge develop. The name develop here just serves to locate commit L.
(This means that instead of git checkout develop; git pull origin develop; git checkout branch1 we could just run git fetch and then git merge origin/develop. The name origin/develop will locate commit L. We don't need to update our own name develop here. In fact, we could probably just delete our name develop entirely! We don't seem to need it any more.)
Git already knows what our current commit hash ID is. Git can find this easily by reading HEAD to get the branch name, and then the branch name to get the hash ID, J. We've now given Git another hash ID, that of commit L. The merge command now needs to be able to turn these various snapshots into changes. How will it do that?
We could try comparing the snapshots in J and L. But that doesn't seem to work. We want to combine changes; that just finds one set of changes: whatever's needed to turn J into L. That's no good; we'd lose our work, if we just took the files from L.
No, the whole notion of combining changes calls for something else. We need to find some sort of common starting point. We need a commit where we, on our way to our commit J, and they, on the way to their commit L, were both in agreement.
So, look back at our drawing. At commit G, we were both in agreement: we were both using commit G, with its snapshot. At commit H we were still in agreement, both using H and its snapshot. But then we went to I, which they don't have—we haven't given it to them yet, so they still don't have it—and they went to K, which we didn't have (but do now). We went on to J and they went on to L. So H is the last shared commit. Git finds this by starting from the two ends, J and L, and working backwards.
Technically, Git is using the Lowest Common Ancestor of a Directed Acyclic Graph here. Not all DAGs have just one LCA, and if there are two or more LCAs, Git has some answers to that, but we'll ignore those cases here. You don't normally need to know all of this, but it's good to remember that the two-or-more-LCAs cases exist, as they can produce surprises. It's also good to be able to eyeball simple graph drawings and identify the obvious LCAs, but when you need to find them in a tangled, realistic graph, just run git merge-base --all to have Git compute them for you.
Anyway, in this case, commit H is the LCA—or in Git's terms, the merge base. It's the best common / shared ancestor commit of your latest commit and theirs. Git now needs to find out what changed, between the common starting point—commit H—and your latest commit. Git could do this one commit at a time: H-vs-I, then I-vs-J, and that would sometimes have some advantages, but Git just does it all at once by comparing the snapshot in H to the one in J, as if you had run:
git diff --find-renames <hash-of-H> <hash-of-J>
Whatever this diff finds, that's what you changed. Then, Git runs a second diff:
git diff --find-renames <hash-of-H> <hash-of-L>
Whatever this diff finds, that's what they changed.
Merge now simply (hah) combines these two sets of changes. If all goes well, Git can combine these on its own. Git will apply the combined changes to the snapshot from the merge base, in this case H. That way you get your changes kept, and their changes added.
Last—and again this is only automatic if all goes well in the combining stage—Git will now make a new merge commit. This is a slightly special kind of commit. It's just like an ordinary commit in all ways but one: it has, as a parent, your current commit J. It has a snapshot, as all commits do. What makes it special, though, is that it has a second parent: commit L, the one Git found by the argument you gave to git merge.
We can draw the result like this:
I--J
/ \
...--G--H M <-- branch1 (HEAD)
\ /
K--L <-- develop, origin/develop
Git writes the new merge commit's hash ID into the current branch name as usual, so now the name branch1 points to new merge commit M. The snapshot in M contains the result of Git's combining work, as applied to the snapshot from H, the merge base. While there's no easy way to see the merge base, we can always have Git find it for us:
git merge-base --all <hash-of-J> <hash-of-L>
will spit out the hash ID of commit H. Since no commit can ever be changed once it's made, the backwards connections from J and L will always lead backwards to H. (Commit M can't be changed either, now that it exists. It will always have this snapshot and will always point back to both J, as its first parent, and L, as its second.)
This is a true merge, and the process that makes a true merge combines your work with their work. The build_version.txt file in commit M will be the result of applying the combined changes—yours and theirs—as seen from the merge base. If you changed the file and they didn't, the file will match your file, because they made no changes. Just because their file is different from yours doesn't mean you'll get their file. You get the merge base file, plus your changes, plus their changes. That's what merge does, by design. There are a whole bunch of special cases you can add on here, but this is the basic form.