Why does Git create commit histories for files I haven't changed?

Tech Knowledge
Published on July 2, 2025
Article Image

Introduction

If you've used Git, have you ever been confused by a commit being automatically generated for files you didn't change? These automatically generated commits have commit messages like Merge branch 'hoge' of ・・・. It seems a merge has occurred, but the commit content isn't from files you modified. However, the committer is you. You have your own modified files, but they are unrelated to the automatically committed files, so no conflict should have arisen. This behavior doesn't occur with other source control tools like SVN, so it seems unnatural.

Why is a merge necessary even when there should be no conflict? This time, we will explain this question.1

Terminology: Merge Commit

The automatically generated commit here is a merge commit. A merge commit is a special commit created when divergent histories are integrated. Merge commits are created at two timings:

  • When explicitly merging two branches
gitGraph
    commit id:"C1"
    commit id:"C2"
    branch develop
    checkout develop
    commit id:"C3"
    checkout main
    commit id:"C4"
    checkout develop
    checkout main
    merge develop id:"Explicit Merge"
  • When pulling and the remote and local histories have diverged
---
config:
  gitGraph:
    mainBranchName: "origin/main"
---
gitGraph
    commit id:"C1"
    commit id:"C2"
    branch main
    checkout main
    commit id:"C3"
    checkout "origin/main"
    commit id:"C4"
    checkout main
    commit id:"Pull here" type:HIGHLIGHT
    commit id:"Auto-generated Commit"

The automatically generated commit falls into the latter category. Hereafter, this automatically generated commit will be referred to as a merge commit.

What is Divergence in Git?

We talk about "integrating divergent histories," but what state does divergence refer to in Git? Divergence in Git is not limited to "creating a branch."

Diverged State

---
config:
  gitGraph:
    mainBranchName: "origin/main"
---
gitGraph
    commit id:"C1"
    commit id:"C2"
    branch main
    checkout main
    commit id:"C3"
    checkout "origin/main"
    commit id:"C4"

Even in the case shown above, Git considers the history to be diverged.

  • The remote (origin/main) has a C4 commit pushed by someone else.
  • The local (main) has a C3 commit you committed.

This is a diverged state. In other words, "divergence" in Git simply refers to a state where the history has extended in two directions, regardless of whether a branch was explicitly created. If you perform a pull in this state, a merge commit will be created because "the remote and local histories have diverged."

Non-Diverged State

Now, what about the following state?

---
config:
  gitGraph:
    mainBranchName: "origin/main"
---
gitGraph
    commit id:"C1"
    commit id:"C2"
    branch main
    checkout main
    commit id:"C3"
  • There are no unpulled commits on the remote.
  • There are unpushed commits locally.

This is not diverged. The local is merely "ahead" of the remote; there is no branching. If you perform a pull, nothing happens because there are no commits to pull from the remote.

Next, let's look at the following case:

---
config:
  gitGraph:
    mainBranchName: "origin/main"
---
gitGraph
    commit id:"C1"
    commit id:"C2"
    branch main
    commit id:"No commits" type:HIGHLIGHT
    checkout "origin/main"
    commit id:"C4"
  • There are unpulled commits on the remote.
  • There are no unpushed commits locally.

This is also not diverged. The remote is merely "ahead" of the local; there is no branching. If you perform a pull here, the remote C4 commit is simply added to the local history (no merge commit is created).

Summary of Divergence

The states are summarized in the table below:

Unpulled commits on remote Unpushed commits locally Divergence
No No No
No Yes No
Yes No No
Yes Yes Yes, diverged

When in this "Yes, diverged" state, performing a pull will result in a merge commit.

Necessity of Merge Commits

We understand the conditions under which merge commits are created, but why are merge commits necessary?

---
config:
  gitGraph:
    mainBranchName: "origin/main"
---
gitGraph
    commit id:"C1"
    commit id:"C2"
    branch main
    checkout main
    commit id:"C3"
    checkout "origin/main"
    commit id:"C4"
    checkout main
    commit id:"Pull here" type:HIGHLIGHT
    commit id:"C5 Merge Commit"

If you push here, the state becomes as follows:

---
config:
  gitGraph:
    mainBranchName: "main origin/main"
---
gitGraph
    commit id:"C1"
    commit id:"C2"
    branch "Diverged History"
    checkout "Diverged History"
    commit id:"C3"
    checkout "main origin/main"
    commit id:"C4"
    checkout "Diverged History"
    commit id:"Pull here" type:HIGHLIGHT
    commit id:"Push" type:HIGHLIGHT
    checkout "main origin/main"
    merge "Diverged History" id:"C5 Merge Commit"

The diverged main and origin/main are integrated (merged) at the merge commit. This means that a merge commit contains information about when the divergent histories were integrated. Without a merge commit, such a branching history cannot be preserved. Therefore, without a merge commit, the history would look like a single line as shown below:

---
config:
  gitGraph:
    mainBranchName: "main origin/main"
---
gitGraph
    commit id:"C1"
    commit id:"C2"
    commit id:"C4"
    commit id:"C3"

Preserving this branching history is the reason for the existence of merge commits.2

Automatically Generated Merge Commits Do Not Contain File Change History

Here's another question:

---
config:
  gitGraph:
    mainBranchName: "main origin/main"
---
gitGraph
    commit id:"C1"
    commit id:"C2"
    branch "Diverged History"
    checkout "Diverged History"
    commit id:"C3"
    checkout "main origin/main"
    commit id:"C4"
    checkout "Diverged History"
    checkout "main origin/main"
    merge "Diverged History" id:"C5 Merge Commit"

Since the C4 commit history already exists, wouldn't creating a merge commit result in a duplicate C4 change history? The truth is, automatically generated merge commits (when there are no conflicts) do not contain file change history. The reason it appears as if the change history is duplicated is due to a misunderstanding of GUI tool display results. Key points are as follows:

  • GUI tools display the difference with the parent commit.
  • Unlike regular commits, merge commit C5 has two parents3 (C3 and C4).
  • If C3 is used as the base, the C4 changes appear as a diff.
  • If C4 is used as the base, there is no diff.

Below are examples from SourceTree (Figure 1, Figure 2). In SourceTree, you can select the parent commit to base the display on using the gear icon.

Figure 1. With diffFigure 1. With diff Figure 2. Without diffFigure 2. Without diff

You can also confirm that merge commits do not contain file change history using Git commands.

C4 commit (file changes are displayed)

> git --no-pager show 264ab16
commit 264ab16a01f17f256ec902668742ae174be7de75
Author: UserA <korochin0911@gmail.com>
Date:   Sun Sep 7 09:41:19 2025 +0900

    Commit 4

diff --git a/FileX.txt b/FileX.txt
index 7a13578..9ebd7de 100644
--- a/FileX.txt
+++ b/FileX.txt
@@ -1 +1 @@
-FileX
\ No newline at end of file
+FileX Commit 4
\ No newline at end of file

C5 merge commit (only the commit message is displayed)

> git --no-pager show fcf152f
commit fcf152f06c075c2aee6d613bad64059cf31f386d (HEAD -> main, origin/main, origin/HEAD)
Merge: 6a241b3 264ab16
Author: UserB <korochin0911@gmail.com>
Date:   Sun Sep 7 09:43:10 2025 +0900

    Merge branch 'main' of https://github.com/korochin0911/hoge

Therefore, while the title of this note was "Why are commit histories for files I haven't changed created?", the correct statement is "It appears as if commit histories for files I haven't changed are created, but they actually are not."4

Means to Avoid Generating Merge Commits

We have explained why merge commits are generated. While merge commits are necessary to preserve branching history, Git offers several ways to avoid creating such history if you don't want it.

Rebase

The default behavior when pulling (git pull) is merge.

>git config pull.rebase
false

Changing this setting to true makes the pull operation rebase.

>git config pull.rebase true

With rebase, no merge commit is created.

---
config:
  gitGraph:
    mainBranchName: "origin/main"
---
gitGraph
    commit id:"C1"
    commit id:"C2"
    branch main
    checkout main
    commit id:"C3"
    checkout "origin/main"
    commit id:"C4"

Performing a rebase from this state results in the following:

---
config:
  gitGraph:
    mainBranchName: "origin/main"
---
gitGraph
    commit id:"C1"
    commit id:"C2"
    commit id:"C4"
    branch main
    checkout main
    commit id:"C3"

Pushing results in a linear history like this:

---
config:
  gitGraph:
    mainBranchName: "main origin/main"
---
gitGraph
    commit id:"C1"
    commit id:"C2"
    commit id:"C4"
    commit id:"C3"

The C3 commit is reattached after (or "on top of" in Git terms) the C4 commit, integrating the history. Note that C3's parent was originally C2, but it is rewritten to C4 by rebase. While rebase has the advantage of making history a simple linear line, it has the disadvantage of not preserving the exact branching history.

Squash

Squash is a method of combining multiple commits into one before merging.

---
config:
  gitGraph:
    mainBranchName: "origin/main"
---
gitGraph
    commit id:"C1"
    commit id:"C2"
    branch main
    checkout main
    commit id:"C3"
    checkout "origin/main"
    commit id:"C4"

After squashing:

git merge --squash origin/main
---
config:
  gitGraph:
    mainBranchName: "origin/main"
---
gitGraph
    commit id:"C1"
    commit id:"C2"
    branch main
    checkout main
    commit id:"C3"
    checkout "origin/main"
    commit id:"C4"
    checkout main
    commit id:"C5 Squash"

Pushing results in the following:

---
config:
  gitGraph:
    mainBranchName: "main origin/main"
---
gitGraph
    commit id:"C1"
    commit id:"C2"
    commit id:"C3"
    commit id:"C5"

You might wonder, where did C4 go? In reality, a regular push cannot be performed; a force push (git push --force) is required. The content corresponding to C4 becomes part of C5, but it is a different commit. Since the remote and local histories diverge, a force push is necessary. While no merge commit is created, this method is practically unusable because it destroys the remote history. (The practical use of squash is typically to combine multiple commits from a feature branch into a single commit on main.)

Cherry-pick

cherry-pick is a method to apply a specific commit to another branch.

---
config:
  gitGraph:
    mainBranchName: "origin/main"
---
gitGraph
    commit id:"C1"
    commit id:"C2"
    branch main
    checkout main
    commit id:"C3"
    checkout "origin/main"
    commit id:"C4"

Cherry-pick C4 onto the local main.

---
config:
  gitGraph:
    mainBranchName: "origin/main"
---
gitGraph
    commit id:"C1"
    commit id:"C2"
    branch main
    checkout main
    commit id:"C3"
    checkout "origin/main"
    commit id:"C4"
    checkout main
    commit id:"C4' Cherry-pick"

Pulling then results in the following:

---
config:
  gitGraph:
    mainBranchName: "origin/main"
---
gitGraph
    commit id:"C1"
    commit id:"C2"
    branch main
    checkout main
    commit id:"C3"
    checkout "origin/main"
    commit id:"C4"
    checkout main
    commit id:"C4' Cherry-pick"
    commit id:"C5 Merge Commit"

C4 and C4' have the same changes, but they are different commits. Since the remote and local are diverged, a merge commit is generated. You might think this means it's pointless. In fact, it's not very meaningful, but if anything, the C5 commit generated here will not show file change differences even in SourceTree, so the misunderstanding of "appearing to be changed when I didn't change it" does not occur. However, using cherry-pick for such a purpose is not practical.

Summary

When the remote and local histories have diverged and you perform a pull, a merge commit is generated (by Git's default settings). This commit may seem redundant at first glance, but it is necessary to preserve the branching history. We also showed that while GUI tools' displays can be misleading, making it seem like file changes occurred in a merge commit, in reality, no file change history is included (when no conflicts arose).

There are two types of behavior when pulling: merge (default) and rebase.

  • Merge (default)
    • Pros: Preserves an accurate branching history.
    • Cons: Makes it harder to grasp your own changes.
  • Rebase
    • Pros: History becomes linear and easier to read.
    • Cons: Does not preserve an accurate branching history.

It is necessary to use each appropriately based on their characteristics.

Footnotes

  1. Common reasons given for "commits for files you haven't changed are generated" include the following, but they will not be covered in this note:

    • Line ending issues (git config core.autocrlf, .gitattributes text=auto)
    • Permission changes (git config core.filemode)
    • File changes by auto-formatting tools

  2. More precisely, a commit holds information about its parent commit (the commit before it), so it is clear that C3 branched from C2. However, without a merge commit, it's not known when the histories were integrated (the timing of the branch is known, but not the timing of the reunion).

  3. While merges with three or more parents exist, they are not covered here.

  4. Another way to confirm that merge commits do not contain file change history is to use git log <filename>. In SourceTree, "Show log for selection" corresponds to this. As shown below, merge commits are not included in the file change history.