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

Table of Contents
-
- 3.1. Diverged State
- 3.2. Non-Diverged State
- 3.3. Summary of Divergence
-
- 6.1. Rebase
- 6.2. Squash
- 6.3. Cherry-pick
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 aC4
commit pushed by someone else. - The local (
main
) has aC3
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
andC4
). - If
C3
is used as the base, theC4
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 diff
Figure 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
-
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
- Line ending issues (
-
More precisely, a commit holds information about its parent commit (the commit before it), so it is clear that
C3
branched fromC2
. 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).↩ -
While merges with three or more parents exist, they are not covered here.↩
-
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.↩