Skip to content
academia.sh

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:

  1. Move the branch tip. Done in every mode.
  2. Make the staging area match the target. Done in every mode except --soft.
  3. 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 restore operates on a path; it does not touch history.
  • git reset operates 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.
  • --soft leaves the content in the staging area, --mixed in the working directory; --hard matches both to the target.
  • --hard deletes 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.

Start typing to search.

↑↓ ↵ Esc navigate · open · close