Hunks 🧩
Alright, let's walk through a single example to see Git hunks in action from start to finish.
Imagine we're working on a simple story file called story.txt
.
Initial Setup​
First, let's create our story and make our first commit:
-
Create
story.txt
with the following content:The sun was setting.
A lone wolf howled.
The adventure was about to begin. -
Now, let's add it to Git and commit:
git init
git add story.txt
git commit -m "Initial draft of the story"
Making Changes - Creating Hunks​
Now, let's edit story.txt
to add more details and change some existing lines.
Our story.txt
now looks like this:
The brilliant orange sun was setting over the misty mountains.
A lone grey wolf howled a mournful cry.
The grand adventure was about to begin for our hero.
A hidden path was revealed.
We've made several changes:
- Modified the first line.
- Modified the second line.
- Modified the third line.
- Added a new fourth line.
What is a Hunk and How is it Calculated/Shown?​
When we ask Git what has changed (before staging), Git will show us these modifications grouped into "hunks". A hunk is a contiguous block of lines that Git has identified as added, removed, or modified.
Let's see this with git diff
:
git diff
The output will look something like this (simplified):
diff --git a/story.txt b/story.txt
index <hash1>..<hash2> 100644
--- a/story.txt
+++ b/story.txt
@@ -1,3 +1,4 @@
-The sun was setting.
-A lone wolf howled.
-The adventure was about to begin.
+The brilliant orange sun was setting over the misty mountains.
+A lone grey wolf howled a mournful cry.
+The grand adventure was about to begin for our hero.
+A hidden path was revealed.
- The
@@ -1,3 +1,4 @@
part is the hunk header.-1,3
means this hunk starts at line 1 and spans 3 lines in the old version of the file.+1,4
means this hunk starts at line 1 and spans 4 lines in the new version of the file.
- Lines starting with
-
are lines removed from the old version. - Lines starting with
+
are lines added in the new version. - In this specific diff output, Git might show this as one large hunk because all the changes are contiguous. If the changes were in different parts of the file with unchanged lines in between,
git diff
would show multiple distinct hunks, each with its own@@ ... @@
header.
Git "calculates" these hunks by comparing the two versions of the file (in this case, our working directory version vs. the last committed version) using a diff algorithm. This algorithm identifies the minimal set of changes (additions/deletions) needed to transform the old version into the new version and groups these nearby changes into hunks.
Can we git add
only specific hunks? (Interactive Staging)​
Yes! This is where git add --patch
(or git add -p
) comes in. Let's say we only want to stage the change to the first line ("The sun was setting...") and the newly added last line ("A hidden path was revealed."), but not the changes to the wolf or the third line for now.
When you run:
git add -p story.txt
Git will go through each hunk of changes it found and ask you what to do. For our example, if Git presented the changes as potentially multiple smaller hunks (or if we used s
to split a larger one), we could make specific choices.
Let's assume Git shows us the changes and we interact:
-
Hunk 1 (Modification of the first line):
-The sun was setting.
+The brilliant orange sun was setting over the misty mountains.Git asks:
Stage this hunk [y,n,q,a,d,s,e,?]?
We'll typey
and press Enter. -
Hunk 2 (Modification of the second line):
-A lone wolf howled.
+A lone grey wolf howled a mournful cry.Git asks:
Stage this hunk [y,n,q,a,d,s,e,?]?
We'll typen
and press Enter (we don't want to stage this yet). -
Hunk 3 (Modification of the third line):
-The adventure was about to begin.
+The grand adventure was about to begin for our hero.Git asks:
Stage this hunk [y,n,q,a,d,s,e,?]?
We'll typen
and press Enter. -
Hunk 4 (Addition of the fourth line):
+A hidden path was revealed.
Git asks:
Stage this hunk [y,n,q,a,d,s,e,?]?
We'll typey
and press Enter.
Now, if you run git status
, you'll see that story.txt
is listed as both "Changes to be committed" (for the hunks we staged) and "Changes not staged for commit" (for the hunks we skipped).
Let's commit the staged changes:
git commit -m "Updated sunset description and revealed path"
Our working directory story.txt
still contains all the changes we made initially, but only some of them are committed.
Hunks While Merging & Why Merge Conflicts Occur​
Now, let's set up a scenario for a merge conflict.
-
Create a new branch (
feature-wolf
) from before our last commit (i.e., from "Initial draft of the story") and make a conflicting change to the wolf line.# Let's find the commit hash of the "Initial draft"
# git log
# (Let's say the hash is abc1234)
git checkout -b feature-wolf abc1234 # Create and switch to feature-wolf from the initial commitOn the
feature-wolf
branch, editstory.txt
to be:The sun was setting.
A playful pup barked.
The adventure was about to begin.Notice the second line is different from our
main
branch's eventual version and also different from the initial draft. Now, commit this on thefeature-wolf
branch:git add story.txt
git commit -m "Change wolf to a playful pup" -
Switch back to the
main
branch. Ourmain
branch currently has the sunset and path changes committed. The wolf line in the committed version onmain
is still "A lone wolf howled." However, our working directory onmain
(if we hadn't committed the remaining changes yet) might still have "A lone grey wolf howled a mournful cry."For clarity in this example, let's ensure all earlier unstaged changes on
main
are also committed. The unstaged changes were:-A lone wolf howled.
+A lone grey wolf howled a mournful cry.
-The adventure was about to begin.
+The grand adventure was about to begin for our hero.Let's add and commit these now on
main
:git add story.txt
git commit -m "Further update wolf and adventure line on main"So, on
main
,story.txt
(committed) looks like:The brilliant orange sun was setting over the misty mountains.
A lone grey wolf howled a mournful cry.
The grand adventure was about to begin for our hero.
A hidden path was revealed. -
Attempt the merge and experience a conflict:
git merge feature-wolf
Git will likely output something like:
Auto-merging story.txt
CONFLICT (content): Merge conflict in story.txt
Automatic merge failed; fix conflicts and then commit the result.
Why did this conflict occur, in terms of hunks?​
-
Common Ancestor: Git looks at the common ancestor commit of
main
andfeature-wolf
. In our case, it's "Initial draft of the story". In that version, the second line was "A lone wolf howled." -
Changes on
main
: Since the common ancestor, themain
branch changed the hunk containing "A lone wolf howled." to "A lone grey wolf howled a mournful cry." -
Changes on
feature-wolf
: Since the common ancestor, thefeature-wolf
branch changed the same hunk containing "A lone wolf howled." to "A playful pup barked."
Git sees that both branches have modified the same section (hunk) of the file relative to their common ancestor, but they've changed it in different ways. Git doesn't know which version to keep:
- Should it be the "grey wolf" from
main
? - Should it be the "playful pup" from
feature-wolf
?
Because Git cannot make this decision automatically, it raises a merge conflict. It will mark the conflicting hunks in story.txt
like this:
The brilliant orange sun was setting over the misty mountains.
<<<<<<< HEAD
A lone grey wolf howled a mournful cry.
The grand adventure was about to begin for our hero.
A hidden path was revealed.
=======
A playful pup barked.
The adventure was about to begin.
>>>>>>> feature-wolf
<<<<<<< HEAD
: This indicates the start of the conflicting hunk from your current branch (main
).=======
: This separates the conflicting hunks.>>>>>>> feature-wolf
: This indicates the end of the conflicting hunk from the branch you're merging (feature-wolf
).
Notice that the lines about the "sun", "adventure", and "path" might also be part of the conflict markers if Git considered the surrounding changes as part of the same conflicting region. The exact boundaries of the conflict markers depend on how Git's diff algorithm groups the changes.
To resolve this, you would:
- Open
story.txt
. - Manually edit the file to choose which version of the wolf line you want (or combine them, or write something new). For example, you might decide to keep the "grey wolf" and the "hero" line from main, and also perhaps incorporate the spirit of the "playful pup" if you wanted, or simply choose one version.
Let's say you decide on:
The brilliant orange sun was setting over the misty mountains.
A lone grey wolf howled a mournful cry.
The grand adventure was about to begin for our hero.
A hidden path was revealed. - Remove the
<<<<<<<
,=======
, and>>>>>>>
markers. - Add the resolved file:
git add story.txt
. - Commit the merge:
git commit -m "Merge feature-wolf, resolving wolf description conflict"
.
So, hunks are fundamental:
- They show you what changed.
git add -p
lets you stage them individually.- During a merge, Git compares hunks of changes from both branches against their common ancestor. If both branches changed the same hunk in different ways, you get a conflict.