Search code examples
gitgit-branchgit-merge

Does the master branch get modified when I merge it into another branch?


Simple question:

I recently found out that you can merge your master into derived feature branches (to update your feature branch with the latest master changes).

By doing so, am I modifying the master in any way? Or is it completely unaltered by that operation?


Solution

  • The short answer is "no", but this deserves more than a short answer.

    You'll find that you and Git get along a lot better if you think in terms of commits instead of in terms of branches. Well, more precisely, it's important to remember which branch is the current branch—or use git status and see if it says on branch master or on branch develop or whatever—but after that point, think in terms of commits.

    Each branch name points to one (single) commit. That commit is the tip commit of the branch, by definition. The phrase points to here means that the branch name literally contains the raw hash ID of that one commit. Each commit has its own unique hash ID: no other commit can ever have that hash ID. That hash ID means that commit, now and forever.

    The special name HEAD refers to a branch name.1 That is, HEAD remembers one branch name, and the one branch name remembers one commit.

    When you do:

    git checkout develop
    

    (assuming it succeeds), Git puts you "on" your develop, by making HEAD remember the name develop. Your current commit is now the one that develop names.

    When you subsequently run:

    git merge master
    

    (assuming it succeeds), Git has done all the work of merging—whatever work that is, which can get complicated—and then perhaps made a new commit.2 If it did make a new commit, the name develop now identifies the new commit. The name master still identifies the same commit that the name master identified all along.

    Each commit, meanwhile, refers to some number of parent commits, by their hash IDs. Most commits hold just one parent hash ID, so most commits point backwards to their one parent. This series of backwards-pointing arrows forms a chain. For instance, perhaps the name master holds hash ID H. Meanwhile, the commit with hash ID H holds hash ID G, and commit G holds hash ID F, and so on:

    ... <-F <-G <-H   <-- master
    

    Git can go from the name, master, to commit H, then back to G, to F, and so on, backwards through the chain of commits.

    No commit can ever change—not one bit—so once a commit exists and has some parents, that commit always keeps pointing backwards to those parents. (You cannot change a commit. At most, you can get Git to stop looking at a commit. If neither you nor Git can find the commit, the commit eventually falls out of the repository and is garbage collected.) Git mainly just keeps adding new commits to the chain.

    So we start out with something like this:

    ...--F--G--H   <-- master
             \
              I--J   <-- develop
    

    You git checkout develop to select commit J. (Draw the word HEAD next to the name develop, so that you can remember that you're on develop.)

    Now you run git merge master and Git fires up the merge machinery. That does a lot of computing, to figure out the right merge result, and builds a new commit. Since the new commit is a merge commit, it points back to J as usual, but also to H:

    ...--F--G-----H   <-- master
             \     \
              I--J--K
    

    As a result of writing out merge commit K, Git writes its hash ID—whatever that really is—into the name to which HEAD is attached, and now develop points to commit K:

    ...--F--G-----H   <-- master
             \     \
              I--J--K   <-- develop (HEAD)
    

    Commit H is not changed—it can't be—and the name master has not moved, so master is not affected.


    1The name HEAD can contain a hash ID instead of a branch name. In this case, Git says that you are in "detached HEAD" mode. Updates—such as creation of new commits—just write the new commit hash ID into HEAD directly, so that HEAD continues to be detached. Use git checkout with a branch name to re-attach HEAD to that branch name.

    2In some cases, git merge doesn't actually do a merge at all, but rather does a fast-forward operation. This occurs when there's no merge needed. For instance, if you're on master and have this:

    ...--G--H   <-- master (HEAD)
             \
              I--J  <-- dev
    

    and ask to git merge dev, the changes that Git would have to combine, if it were to do a real merge, would be whatever changed from commit H to commit H ("what you changed" on master) with whatever changed from commit H to commit J ("what they changed" on dev). But obviously what's in commit H is the same as what's in commit H. There's no need to actually do any merging! If you permit it, git merge will not bother to merge at all, and will just check out commit J directly and drag the name master forward to match, giving:

    ...--G--H
             \
              I--J  <-- dev, master (HEAD)
    

    Note that this is for git checkout master; git merge dev, not the other way around. Doing a git checkout dev; git merge master would get you an "already up to date" message with nothing else changed at all.