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 aC4commit pushed by someone else. - The local (
main) has aC3commit 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
C5has two parents3 (C3andC4). - If
C3is used as the base, theC4changes appear as a diff. - If
C4is 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,.gitattributestext=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
C3branched 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.
↩