Lesson 12 / 15
Reset Modes
Moving the branch tip backward; the effect of the `--soft`, `--mixed`, and `--hard` modes on the three regions, lost content, and recovery through the reflog.
Published Updated
In this lesson
- Determines which regions resetting affects, by mode
- Drops a commit from history while keeping its content in the desired region
- Recovers a commit that has been reset, from the reflog
Contents
Amending moved the branch tip to a new commit attached to the same parent. Reset moves the branch tip further back in history: one or more commits drop out of the branch’s reachable past. Reset has three modes, and the difference among them is which regions are affected.
This lesson applies all three modes, one by one, to the same mistake.
The Mistake
The term list is meant to be sorted alphabetically and is handed to a general-purpose
sorting tool. The language setting is turned off so the tool works on byte values
(LC_ALL=C); this way the result comes out the same on any machine:
LC_ALL=C sort terimler.txt -o terimler.txt
cat terimler.txt
ağaç | tree bağlı liste | linked list dizi | array karma tablosu | hash table kuyruk | queue küme | set yığıt | stack çizge | graph öbek | heap
The order is wrong. “çizge” and “öbek” have dropped to the end of the list, and “küme” comes after “kuyruk.” The reason is that the sort is done by byte value, not by the Turkish alphabet: as seen in the How Computers Work course, letters outside the ASCII range are encoded in UTF-8 with more than one byte, and their first byte is greater than every ASCII letter. The correct order requires a locale-aware comparison scheme.
Without the mistake being noticed, it is committed. Since the same commit will be rewritten several times in this lesson, the date is fixed first; these two variables stay defined in the open session, so retype them if you opened a new terminal:
export GIT_AUTHOR_DATE="2024-03-08 15:00:00 +0300" export GIT_COMMITTER_DATE="2024-03-08 15:00:00 +0300" git commit -a -m "Terim listesini abecesel sırala"
[main baf5138] Terim listesini abecesel sırala 1 file changed, 5 insertions(+), 5 deletions(-)
What Reset Does
Reset consists of three steps, and the mode determines how many of them get applied:
- Move the branch tip. Done in every mode.
- Make the staging area match the target. Done in every mode except
--soft. - Make the working directory match the target. Done only with
--hard.
The first step touches history; the second and third steps touch content.
--soft
git reset --soft HEAD~1 git log --oneline -n 1
377b1f8 README'ye kullanım bölümü ekle
The commit dropped out of history. What happened to the content?
git status --short
M terimler.txt
The change is standing in the staging area. Only the first step was applied: the branch tip moved back, the staging area and the working directory were left as they were. The result is the state of “the commit was never written, but the content is ready to be committed.”
git diff --staged --stat
terimler.txt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-)
This mode is used to rearrange the scope of the last commit: the commit is undone, the content is split the way it is wanted, and it is recommitted.
--mixed
The reset commit is rewritten with the same message, and this time reset with the default mode:
git commit -m "Terim listesini abecesel sırala"
[main baf5138] Terim listesini abecesel sırala 1 file changed, 5 insertions(+), 5 deletions(-)
git reset HEAD~1
Unstaged changes after reset: M terimler.txt
git status --short
M terimler.txt
The mark moved to the second column: the content is no longer in the staging area, only
in the working directory. The first and second steps were applied. This is the default
mode even when --mixed is not written.
This mode undoes both the commit and the selection made in the staging area; it is used when the decision of which files to commit together needs to be made from scratch.
--hard
The commit is written once more; the change in the working directory is staged with -a:
git commit -a -m "Terim listesini abecesel sırala"
[main baf5138] Terim listesini abecesel sırala 1 file changed, 5 insertions(+), 5 deletions(-)
git reset --hard HEAD~1
HEAD is now at 377b1f8 README'ye kullanım bölümü ekle
git status --short
The output is empty. The term list has also returned to its old state: the sort has been completely undone. All three steps were applied.
This mode is destructive and takes two separate pieces of content down with it: the
content of the reset commits, and every uncommitted edit that was in the working
directory at the time. The first can be brought back from the reflog; the second cannot.
Looking at git status’s output before running git reset --hard is the only way to
avoid this second loss.
Comparing the Three Modes
| Mode | Branch tip | Staging area | Working directory | Where the change stays |
|---|---|---|---|---|
--soft |
Moved | Untouched | Untouched | In the staging area |
--mixed |
Moved | Matched | Untouched | In the working directory |
--hard |
Moved | Matched | Matched | Nowhere |
The table reduces to a single sentence: the harsher the mode, the deeper the undo reaches. The choice is made based on whether the commit’s content should be preserved.
Recovery
A reset commit stays in the object database and can be found through the reflog:
git reflog -n 6
377b1f8 HEAD@{0}: reset: moving to HEAD~1
baf5138 HEAD@{1}: commit: Terim listesini abecesel sırala
377b1f8 HEAD@{2}: reset: moving to HEAD~1
baf5138 HEAD@{3}: commit: Terim listesini abecesel sırala
377b1f8 HEAD@{4}: reset: moving to HEAD~1
baf5138 HEAD@{5}: commit: Terim listesini abecesel sırala
The log has recorded the order the three modes were tried in exactly as it happened. It
is not a coincidence that the commit ID comes out as baf5138 every time: as long as
the content, message, author, and date are the same, the ID is the same too. This is a
direct consequence of content-addressed storage.
The branch tip is moved back to the desired ID:
git reset --hard baf5138
HEAD is now at baf5138 Terim listesini abecesel sırala
The commit is back in history. In this example the sort really is wrong, so the final decision is to drop it:
git reset --hard HEAD~1
HEAD is now at 377b1f8 README'ye kullanım bölümü ekle
The Form Given a Path
When git reset is given a path, its behavior changes entirely: the branch tip does not
move, only that path’s record in the staging area is returned to its state at the
target. To try it, the list is sorted again:
LC_ALL=C sort terimler.txt -o terimler.txt git add terimler.txt git status --short
M terimler.txt
git reset terimler.txt
Unstaged changes after reset: M terimler.txt
git status --short
M terimler.txt
This is the older form of what was done with git restore --staged in the previous
lesson. The same command name doing different things depending on whether it is given a
path explains why git restore was named separately.
ORIG_HEAD
Before moving the branch tip, reset writes the old value to a file named ORIG_HEAD:
cat .git/ORIG_HEAD
baf5138febc3dd9acb9ef94162c9d2f72e8e5d0a
This name can be used like a ref; the shortest way to undo a reset done by mistake is to
go back to the ORIG_HEAD target without reading the reflog:
git reset --hard ORIG_HEAD
HEAD is now at baf5138 Terim listesini abecesel sırala
The ORIG_HEAD file records only the last operation; it is overwritten on successive
resets. Going further back requires the reflog.
Since the sort is wrong, it is dropped again after this trial; the next lesson starts from the
state at 377b1f8:
git reset --hard HEAD~1
HEAD is now at 377b1f8 README'ye kullanım bölümü ekle
The Difference Between Reset and Restoring a File
The two commands are confused because both are called “undoing.” The distinction is clear:
git restoreoperates on a path; it does not touch history.git resetoperates on a ref; it moves the branch tip.
If you want to return a file to an old state, use git restore; if you want to drop a
commit from history, use git reset.
Reset itself is also problematic on shared history: moving the branch tip back creates incompatibility with copies that already have those commits. The previous lesson’s rule holds here too — shared history is not rewound.
Summary
- Reset moves the branch tip; the mode determines whether the staging area and the working directory are also matched to it.
--softleaves the content in the staging area,--mixedin the working directory;--hardmatches both to the target.--harddeletes uncommitted edits irrecoverably.- Reset commits stay in the object database; they can be found and brought back through the reflog.
- The same content, message, author, and date always produce the same commit ID.
Next Step
Reset and amending both work by rewriting history, and this is why they cannot be used on shared commits. So what happens if a shared commit is wrong? The next lesson takes up the tool that reverses a commit’s effect without disturbing history — reverting.
To keep your progress and take notes, Log in
My notes
Log in to take notes.