In lesson 03-02 we came across this message for the first time:

error: Your local changes to the following files would be overwritten by checkout:
	app.js
Please commit your changes or stash them before you switch branches.

And we said there was a third way out, besides committing or discarding: setting the changes aside. We promised a whole lesson. This is it.

git stash is Git's odds-and-ends drawer: it takes everything you have half done in the working tree and in the index, saves it somewhere safe, and leaves your working copy as clean as if you had just cloned. Afterwards, whenever you like, you get it back.

The scenario is always the same and it happens to everyone: you are halfway through something, with code that does not build and does not deserve a commit, and an emergency comes in. Carla is going to live through it in this lesson. And at the end we shall look at what almost nobody looks at: that there is nothing magical about the stash, that underneath it is made of ordinary commits on a hidden reference, exactly the same objects from the data model of lesson 01-04.

Contents

  1. The scenario: Carla and the emergency
  2. git stash: what it saves and what it does not
  3. Untracked and ignored files: -u and -a
  4. The stack: list, show, apply, pop, drop, clear
  5. apply versus pop
  6. Referring to a specific entry
  7. git stash push: messages, paths and interactive mode
  8. --keep-index and --staged
  9. git stash branch: when the stash no longer fits
  10. How it works underneath
  11. Risks: forgotten stashes and false backups

  1. The scenario: Carla and the emergency

Carla is on feature/sort-by-date, halfway through a function. She has touched app.js and styles.css, and has created a new file, utils.js, which she has not yet added to the repository.

git status --short
 M app.js
 M styles.css
?? utils.js

A message arrives from Ana: the delete button does not work in production and it has to be fixed now. Carla needs to switch to main, make the fix and come back. Her options:

Option Problem
Commit a wip It clutters the history (even though 05-02 can fix that), and her code does not build
Discard with git restore She loses two hours of work
Copy the files to /tmp by hand It works, but it is craft work and it gets forgotten
git stash Sets everything aside, leaves the working tree clean and gives it back afterwards
git stash push -u -m "Sort by date, half done"
Saved working directory and index state On feature/sort-by-date: Sort by date, half done
git status
On branch feature/sort-by-date
nothing to commit, working tree clean

A clean working tree. Carla can switch branch without Git objecting, fix the bug, publish it and come back:

git switch main
git pull
# ... fixes, commits, publishes ...
git switch feature/sort-by-date
git stash pop
On branch feature/sort-by-date
Changes not staged for commit:
	modified:   app.js
	modified:   styles.css

Untracked files:
	utils.js

Dropped refs/stash@{0} (a7f3c92e8b1d5c4a7f2e9b6d3c8a1f5e7b4d2c9a)

Everything is back as it was. That is git stash at its most basic. Now for the details that make the difference.

  1. git stash: what it saves and what it does not

git stash with no arguments is an alias for git stash push. By default it saves:

  • The changes to tracked files that are modified in the working tree.
  • The changes to tracked files that are staged in the index.

And it does not save:

  • Untracked files (the ones that appear as ?? in git status).
  • Files ignored by .gitignore.

This is the first trap, and it is a serious one. Had Carla run git stash without -u:

git stash
git status --short
?? utils.js

app.js and styles.css would have been set aside, but utils.js would still be there, untracked and unstashed. And since it does not set it aside, it does not give it back either: if Carla deleted it by mistake thinking it was in the stash, she would lose it.

Git's logic makes sense — an untracked file has never been part of the repository, so Git is conservative and does not touch it — but the practical consequence always comes as a surprise the first time.

Recalling the three areas from lesson 01-03, here is how they are treated:

Area Set aside by git stash?
Working tree, modified tracked files Yes
Index (staging area) Yes
Untracked files Only with -u
Ignored files Only with -a
Repository (commits) Never: the stash does not touch commits

And a nuance that matters when you restore: by default, the stash does not preserve what was staged and what was not. On pop or apply, everything comes back as "modified, unstaged". If you need that distinction preserved, there is --index:

git stash pop --index

With --index, what was in the index goes back to the index. It can fail if the current state does not allow it, in which case Git applies everything unstaged and warns you.

  1. Untracked and ignored files: -u and -a

Option Long name What it adds to what is saved by default
-u --include-untracked Untracked files
-a --all Untracked files and ignored ones
# The usual thing when you have created new files
git stash -u

# Only if you know exactly what you are doing
git stash -a

On -a: ignored files are usually node_modules/, dist/, .env, build artefacts… Setting them aside means deleting them from the working tree and putting them into the stash. With node_modules/ that is tens of thousands of files and an extremely slow operation. And with .env, your local credentials end up inside Git objects, which is precisely what you do not want. Use -a exceptionally and deliberately.

-u, on the other hand, is so frequently what you want that many people configure it as the default behaviour with an alias:

git config --global alias.save 'stash push -u'

Aliases are covered thoroughly in lesson 06-04, but this one is worth having straight away.

  1. The stack: list, show, apply, pop, drop, clear

The stash is not a single drawer: it is a stack. You can set things aside several times, and each new entry goes on top.

git stash list
stash@{0}: On feature/sort-by-date: Sort by date, half done
stash@{1}: WIP on main: 7d3a8f4 Return focus to the text field after deleting
stash@{2}: On feature/colour-labels: colour picker test
  • stash@{0} is always the most recent one.
  • The numbers are renumbered every time you add or remove an entry. Today's stash@{1} is not tomorrow's. That is an excellent reason to write descriptive messages.
  • The text WIP on main: 7d3a8f4 ... is the automatic message when you do not give it one: the branch and the commit it was saved on top of.

To see the content of an entry:

# A summary of the files
git stash show stash@{0}
 app.js     | 12 ++++++++----
 styles.css |  5 +++++
 2 files changed, 13 insertions(+), 4 deletions(-)
# The full diff
git stash show -p stash@{0}
diff --git a/app.js b/app.js
index 8c3d5a1..2e7b9f4 100644
--- a/app.js
+++ b/app.js
@@ -22,7 +22,11 @@ function renderList() {
   const list = document.getElementById('task-list');
   list.innerHTML = '';
-  tasks.forEach(function (t) {
+  const sorted = tasks.slice().sort(function (a, b) {
+    return b.created - a.created;
+  });
+  sorted.forEach(function (t) {
     list.appendChild(createTaskElement(t));
   });

One detail: by default, git stash show does not include the untracked files you may have saved with -u. To see them:

git stash show -p -u stash@{0}

The full repertoire of commands:

Command What it does
git stash / git stash push Sets the changes aside and cleans the working tree
git stash list Lists the stack
git stash show [-p] [<entry>] Shows what is in an entry
git stash apply [<entry>] Applies the changes, keeping the entry
git stash pop [<entry>] Applies the changes and removes the entry
git stash drop [<entry>] Removes the entry without applying it
git stash clear Empties the whole stack. Without confirmation
git stash branch <branch> [<entry>] Creates a branch from the entry and applies it
git stash create Creates the stash object without touching the stack or the working tree
git stash store <sha> Stores an object created with create on the stack

git stash clear deserves a warning in bold: it deletes the whole stack in one go and does not ask. Recovering something afterwards is possible but awkward (you have to rummage for orphaned objects with git fsck, a technique from lesson 09-04). Treat it like rm -rf.

  1. apply versus pop

Both apply the saved changes to your working tree. The difference is what happens to the entry afterwards:

git stash apply git stash pop
Applies the changes Yes Yes
Removes the entry from the stack No Yes, if the application succeeded
Can be applied on several branches Yes No (it disappears after the first)
If there is a conflict The entry is kept The entry is kept
Risk of duplicating changes Yes, if you forget to drop No
Risk of losing what you saved No Low, but real

What this means in practice:

Use pop in the normal case: you set things aside, did something else, you come back. It is a single command and it leaves the stack clean.

Use apply when:

  • You want to apply the same changes on two different branches.
  • You are not sure they will fit and you would rather keep the copy until you have checked.
  • The current branch has changed a lot and you suspect there will be a conflict.

The detail that saves lives: if pop causes a conflict, the entry is NOT removed. Git applies what it can, leaves the conflict markers and keeps the stash just in case. It is deliberate behaviour and very sensible. But it has an annoying consequence: after resolving the conflict, the entry is still on the stack and you have to delete it yourself:

git stash pop
Auto-merging app.js
CONFLICT (content): Merge conflict in app.js
The stash entry is kept in case you need it again.
# Resolve the conflict (the mechanics of lesson 03-05)
# ... edit app.js, remove the markers ...
git add app.js

# And now, remove the entry by hand
git stash drop
Dropped refs/stash@{0} (a7f3c92e8b1d5c4a7f2e9b6d3c8a1f5e7b4d2c9a)

Note down that hash drop prints. It is the reference to the stash object, and with it you can recover an entry deleted by mistake (lesson 09-04). It is the same advice we gave when deleting branches in 03-06, and for the same reason.

A warning about stash conflicts: unlike a merge or a rebase, here there is no --abort. If pop conflicts, you are in the middle of the resolution and the only way back is to discard the working tree's changes (git checkout -- . or git reset --hard, with all the care that demands), knowing that the stash is still safe on the stack.

  1. Referring to a specific entry

Almost every command accepts an entry. If you leave it out, stash@{0} is used.

git stash apply stash@{2}
git stash show -p stash@{1}
git stash drop stash@{3}

The stash@{N} notation is the same reflog syntax you saw in 02-06, applied to the refs/stash reference. And it accepts time-based forms:

git stash show stash@{2.hours.ago}
git stash apply stash@{yesterday}

In some shells the braces need quoting or escaping:

git stash apply "stash@{2}"     # safe in bash, zsh and PowerShell

Since Git 2.11 the bare number is accepted too, which is more comfortable:

git stash apply 2      # equivalent to stash@{2}

And remember: the numbers get renumbered. If you have three entries and delete the middle one, what was stash@{2} becomes stash@{1}. Never write a number down on a piece of paper; keep the message.

  1. git stash push: messages, paths and interactive mode

git stash push is the modern, complete form. git stash save "message" is the old form; it still works, but it is discouraged and does not accept paths.

A descriptive message:

git stash push -m "Sort by date: the case of tasks with no date is missing"

It is the cheapest quality-of-life improvement available with this tool. A git stash list with three entries called WIP on main is useless; with three descriptive messages, it is a work plan.

Saving only certain paths:

git stash push -m "Only the styles" styles.css
git stash push -m "Everything in the reports directory" reports/

The changes to those files are set aside; the rest stay in the working tree. It is very useful when you have mixed two tasks in the same session and want to separate one out so you can work on the other in peace.

Interactive mode:

git stash push -p

Hunk by hunk, just like git add -p (lesson 02-04), Git asks you about each block of changes whether you want to set it aside:

@@ -22,7 +22,11 @@ function renderList() {
   const list = document.getElementById('task-list');
   list.innerHTML = '';
-  tasks.forEach(function (t) {
+  const sorted = tasks.slice().sort(function (a, b) {
...
(1/3) Stash this hunk [y,n,q,a,d,j,J,g,/,e,?]?

The keys are the usual ones: y yes, n no, q quit, s split the hunk, e edit it by hand.

Other push options:

Option What it does
-m <message> A descriptive message
-u / -a Include untracked / also ignored files
-p Choose hunk by hunk
-k / --keep-index Leaves what was staged untouched (section 8)
-S / --staged Sets aside only what is staged (section 8)
-q Quiet
--pathspec-from-file=<f> Reads the list of paths from a file

  1. --keep-index and --staged

Two options with similar names and very different effects. The table clears it all up; suppose you have app.js staged and styles.css modified but unstaged:

Option What goes into the stash What is left in the working tree
(none) app.js + styles.css Nothing: a clean working tree
--keep-index app.js + styles.css app.js staged (everything else clean)
--staged Only app.js styles.css modified

--keep-index saves everything but restores in the working tree what was in the index. Its classic use is testing a commit before making it:

# I have staged exactly what I want to commit
git add app.js
git stash push --keep-index -m "What does not belong in this commit"

# The working tree contains ONLY what I am about to commit: I can genuinely test it
npm test

# If it passes, I commit knowing I tested that and nothing else
git commit -m "Add sorting by creation date"

# And I get the rest back
git stash pop

It is the correct way of making sure a commit is self-contained and does not depend on changes that were left out. Plenty of people discover this way that their "finished" commit did not build on its own.

Note: with --keep-index, the stash contains the staged changes as well. When you pop after the commit, those changes are already committed and can conflict. In practice it is usually combined with --include-untracked and you accept that the later pop sometimes calls for a mental --skip: check with git stash show -p before restoring.

--staged (from Git 2.35) is simpler and newer: it sets aside only what is in the index and leaves the rest. It is the opposite of the previous case, and it serves for "this thing I had already staged, I am taking to another branch":

git add utils.js
git stash push --staged -m "The utility belongs on another branch"
git switch feature/utils
git stash pop

  1. git stash branch: when the stash no longer fits

A classic problem: you saved a stash three days ago, the branch has moved on a lot since then and now git stash pop gives you one conflict after another.

The cause is that the stash was saved on top of one specific commit and is now being applied on top of a completely different one. The solution is to apply it where it fitted:

git stash branch feature/rescue stash@{1}

This command does four things at once:

  1. Creates a new branch at the commit the stash was saved on.
  2. Switches to it.
  3. Applies the stash (which fits perfectly, because the context is the original one).
  4. Removes the entry from the stack, since it has been applied successfully.
Switched to a new branch 'feature/rescue'
On branch feature/rescue
Changes not staged for commit:
	modified:   app.js

Dropped refs/stash@{1} (5c8e2d1f9a3b7e4c6d1f8a2b5e9c3d7f4a1b8e6c)

From there you can commit at your leisure and afterwards integrate the branch with merge or rebase, resolving the conflicts once and with context, instead of wrestling with a blind pop.

It is the best way out when a stash "will not go in". And it is also the best way of turning a stash that has become important into real work.

  1. How it works underneath

This is where the stash stops looking like magic. We pick up the data model of lesson 01-04 again.

When you run git stash, Git creates ordinary commits:

  • A commit with the state of the index.
  • Optionally, a commit with the untracked files (if you used -u).
  • A merge commit whose first parent is the current HEAD, whose second is the index commit, and whose third (if it exists) is the untracked one. This is the stash commit.

And it saves the reference to that commit in refs/stash. Let us check:

git stash push -u -m "Anatomy test"
cat .git/refs/stash
a7f3c92e8b1d5c4a7f2e9b6d3c8a1f5e7b4d2c9a
git cat-file -t a7f3c92
commit

An ordinary commit. Let us look inside it with the same tools as in 01-04:

git cat-file -p a7f3c92
tree 3f8b1c7e2d9a5b4f6c1e8a3d7b2f5c9e4a1d6b8f
parent 7d3a8f4c9b1e5d2a8f7c3b6e9d4a1c8f5b2e7d3a
parent 8e2c5f1a9d3b7e4c1f6a8d2b5e9c3f7a4d1b8e6c
parent 1c9e4b7f2a8d5c3e6b1f9a4d7c2e5b8f3a6d1c9e
author Carla Vidal <[email protected]> 1753959200 +0200
committer Carla Vidal <[email protected]> 1753959200 +0200

On feature/sort-by-date: Anatomy test

Three parents:

Parent What it contains
1st (7d3a8f4) The HEAD at the time you saved: the base
2nd (8e2c5f1) The state of the index
3rd (1c9e4b7) The untracked files (only with -u)

And the commit's own tree is the state of the working tree. With those four trees, Git can reconstruct exactly what you had and apply it as a three-way merge. Hence stash pop conflicts being perfectly ordinary merge conflicts.

The stack, for its part, is the reflog of refs/stash:

git reflog stash
a7f3c92 stash@{0}: On feature/sort-by-date: Anatomy test
5c8e2d1 stash@{1}: WIP on main: 7d3a8f4 Return focus to the text field after deleting
9b4f7e3 stash@{2}: On feature/colour-labels: colour picker test

That explains in one go three things that used to look arbitrary:

  • Why the syntax is stash@{N}: it is exactly the reflog syntax.
  • Why the numbers get renumbered: they are positions in a log, not identifiers.
  • Why deleted stashes can be recovered: the object stays in the database until the garbage collector goes past.

And a very useful practical consequence: since a stash is a commit, you can use any commit command on it.

git show stash@{0}                          # the stash commit
git diff stash@{0}^ stash@{0}               # its diff against the base
git diff main stash@{0} -- app.js           # compare with another branch
git log --oneline stash@{0}^..stash@{0}     # ranges, though here they add little

  1. Risks: forgotten stashes and false backups

Risk 1: the forgotten stash. It is by far the most frequent problem. You set something aside, the emergency drags on, three weeks go by and the work is still there. By the time you find it, it no longer fits anything.

The stash does not appear in git status, does not appear in git log, does not show up in any graphical interface by default and is never sent to the server. It is invisible.

Measures:

# Look at the stack from time to time
git stash list

# Better: add it to your prompt or to an alias you use daily
git config --global alias.st '!git status && echo "--- stash ---" && git stash list'

And the real measure: the stash is for minutes or hours, not for days. If the work is going to wait more than a day, it is a branch. git stash branch exists precisely for that.

Risk 2: believing it is a backup. It is not, for three reasons:

  1. It is local. It is never sent with git push. The default refspec only covers refs/heads/* (lesson 04-05), and refs/stash falls outside it. If your disk dies, the stash dies with it.
  2. It is not cloned. git clone does not bring anybody's stashes. Not even git clone --mirror replicates them usefully.
  3. It is fragile. git stash clear deletes it all without asking. And the stash commits, not being referenced by any branch, are candidates for the garbage collector once they are dropped from the stack.

A real backup is a commit on a published branch. If the work matters, commit it — even with a provisional message you will fix later with rebase -i (lesson 05-02) — and publish it.

Risk 3: applying the stash on the wrong branch. The stash is not tied to a branch: you can pop on any of them. Sometimes that is exactly what you want (moving between branches with your work in tow); sometimes it is an accident that fills main with changes that had no business being there. git stash list tells you which branch each entry was saved on; read it before restoring.

Risk 4: untracked files. We saw it already: without -u they are not saved. The specific and dangerous mistake is running git stash followed by git clean -fd to "leave everything clean": the clean deletes the untracked files the stash did not save, and those really are lost.

Common Mistakes and Tips

Mistake 1: git stash without -u when there are new files. They are left out. It is the number-one surprise with this tool. Always look at git status --short before setting things aside.

Mistake 2: git stash clear to "tidy up". It deletes the whole stack without asking and without confirmation. Use git stash drop <entry> one at a time, after looking at each with show -p.

Mistake 3: thinking pop always removes the entry. If there is a conflict, it keeps it on purpose. After resolving, you have to run git stash drop yourself.

Mistake 4: accumulating stashes with no message. Five entries called WIP on main are five unknowns. git stash push -m "...", always.

Mistake 5: using the stash as a branching system. If it is going to take you more than a day, make a branch. The stash does not survive anybody's memory.

Mistake 6: trusting the numbers. stash@{2} changes meaning the moment you add or remove entries. Identify by message, not by number.

Mistake 7: combining git stash with git clean -fd without thinking. The first does not save untracked files; the second deletes them. The combination destroys new files.

Tip 1: an alias with -u built in. git config --global alias.save 'stash push -u -m' and from then on git save "whatever it is".

Tip 2: --keep-index before committing. A git stash push --keep-index && npm test tells you whether your commit really is self-contained. It takes a minute and prevents broken commits.

Tip 3: git stash show -p before pop. Especially if the entry is more than a day old. Knowing what is about to arrive avoids nasty shocks.

Tip 4: git stash branch as soon as there is a conflict. Do not wrestle with a pop that does not fit: create the branch at the original point, apply it cleanly and merge calmly.

Tip 5: review the stack on Fridays. A weekly git stash list is enough to stop anything sitting there for three months.

Exercises

Exercise 1: the untracked-file trap

In a practice repository:

  1. Modify a tracked file and create a new one without adding it.
  2. Run git stash without -u and check with git status what has happened to each.
  3. Restore, and repeat the operation with -u.
  4. Demonstrate with git stash show -p -u that in the second case the new file really is inside.

Exercise 2: --keep-index to validate a commit

Set up a scenario where you have two changes: one staged (which is valid on its own) and one unstaged (which breaks the file). Using --keep-index:

  1. Set aside what does not belong in the commit.
  2. Check that the file is valid (node --check or similar).
  3. Commit.
  4. Restore the rest and observe what happens.

Exercise 3: the anatomy of a stash

Create a stash with -u and demonstrate with low-level commands:

  1. That refs/stash points at a commit.
  2. That the commit has three parents.
  3. What each of the three contains.
  4. That git reflog stash and git stash list show the same information.

Solutions

Solution 1:

mkdir /tmp/practice-stash && cd /tmp/practice-stash
git init -b main
echo "original" > tracked.txt && git add . && git commit -m "Base"

echo "modified" > tracked.txt
echo "I am new" > untracked.txt
git status --short
 M tracked.txt
?? untracked.txt
# 2. Without -u
git stash
git status --short
ls
?? untracked.txt
tracked.txt  untracked.txt

tracked.txt has gone back to its original version; untracked.txt is exactly where it was. It has not been saved.

# 3. Restore and repeat with -u
git stash pop
git stash -u
git status --short
ls
(no output)
tracked.txt

Now it works: the working tree really is clean and the new file has gone (it is in the stash).

# 4. Check that it is inside
git stash show -p -u stash@{0}
diff --git a/tracked.txt b/tracked.txt
--- a/tracked.txt
+++ b/tracked.txt
@@ -1 +1 @@
-original
+modified
diff --git a/untracked.txt b/untracked.txt
new file mode 100644
--- /dev/null
+++ b/untracked.txt
@@ -0,0 +1 @@
+I am new
git stash pop     # leave everything as it was

Solution 2:

mkdir /tmp/practice-keepindex && cd /tmp/practice-keepindex
git init -b main
echo "const a = 1;" > app.js && git add . && git commit -m "Base"

# The good change, staged
echo "const b = 2;" >> app.js
git add app.js

# The bad change, unstaged
echo "const c = ;" >> app.js

git status --short
MM app.js

(The double M means: modified in the index and modified again in the working tree.)

# 1. Set aside what does not belong in the commit
git stash push --keep-index -m "The half-finished change"
cat app.js
const a = 1;
const b = 2;

The file contains only what was staged.

# 2. Validate
node --check app.js
(no output: correct)
# 3. Commit
git commit -m "Add the b constant"
# 4. Restore
git stash pop
cat app.js
const a = 1;
const b = 2;
const c = ;

The pop has brought back the complete state from before. Since the commit already contains the b line, in this simple case there is no conflict; with changes overlapping on the same lines there would be, and that is why it is worth checking with git stash show -p first.

Solution 3:

mkdir /tmp/practice-anatomy && cd /tmp/practice-anatomy
git init -b main
echo "base" > f.txt && git add . && git commit -m "Base"

echo "change in the working tree" > f.txt
echo "staged" > g.txt && git add g.txt
echo "untracked" > h.txt

git stash push -u -m "Anatomy"
# 1. refs/stash points at a commit
git rev-parse refs/stash
git cat-file -t refs/stash
a7f3c92e8b1d5c4a7f2e9b6d3c8a1f5e7b4d2c9a
commit
# 2. Three parents
git cat-file -p refs/stash | grep '^parent'
parent 4b8e1c7f2a9d5e3b6c1f8a4d7b2e5c9f3a6d1b8e
parent 9d2f6a3c8b1e5f7d4a2c9e6b3f8d1a5c7e4b2f9d
parent 6c1a8f4d3e7b2c5a9f1d6b8e3c7a4f2d5b9e1c8a
# Also with rev-parse, one at a time
git rev-parse refs/stash^1 refs/stash^2 refs/stash^3
# 3. What each one contains
git show --stat refs/stash^1 | head -3     # the base: the original HEAD commit
git ls-tree refs/stash^2                   # the index: includes the staged g.txt
git ls-tree refs/stash^3                   # the untracked files: h.txt
commit 4b8e1c7...
    Base
100644 blob 8c3d5a1...	f.txt
100644 blob 2e7b9f4...	g.txt
100644 blob 5f9c2a8...	h.txt
# 4. The stack is the reflog of refs/stash
git stash list
git reflog stash
stash@{0}: On main: Anatomy
a7f3c92 stash@{0}: On main: Anatomy

The same information, presented two ways. git stash list is, literally, a view of the reflog of refs/stash.

Conclusion

git stash is a small tool with more subtleties than it lets on. The essentials:

  • It sets uncommitted changes aside and leaves the working tree clean, so that you can switch branch, deal with an emergency or try something out, and get them back afterwards.
  • By default it does NOT save untracked or ignored files: that is what -u (untracked, the option you will want almost always) and -a (ignored ones too, only ever used very deliberately) are for.
  • It is a stack: git stash list enumerates it, stash@{0} is the most recent and the numbers get renumbered, so you have to identify entries by message. git stash push -m "..." is compulsory in practice.
  • pop applies and removes; apply applies and keeps. If there is a conflict, pop keeps the entry and you have to drop it by hand after resolving.
  • --keep-index leaves in the working tree only what you were about to commit (so that you can genuinely test it); --staged sets aside only what is staged. And push -p lets you choose hunk by hunk, and push <path> lets you set aside only certain files.
  • git stash branch <branch> creates a branch at the stash's original commit and applies it there: the best way out when a stash no longer fits.
  • Underneath there is no magic: they are ordinary commits on refs/stash, with the original HEAD, the index and the untracked files as parents, and the stack is that reference's reflog. That is where the stash@{N} syntax and the renumbering come from.
  • It is not a backup: it is local, it is not pushed, it is not cloned and clear deletes it without asking. For minutes and hours, not for days.

What comes next

Up to here, the whole module has been about modifying the history: reapplying it, reorganising it, copying it, setting it aside. Now we are going to do the opposite: fix a point in it for good.

task-manager is about to have its first stable version. The team needs to be able to say "this is 1.0.0" and, two years from now, when an issue arrives from a client still on that version, for somebody to be able to stand exactly on that code without having to remember a forty-character hash.

That is what tags are for, the fourth type of object in Git's database that we met in lesson 01-04 and have barely mentioned since. We shall see them in lesson 05-05: Tagging Commits.

Mastering Git: From Beginner to Advanced

Module 1: Introduction to Git

Module 2: Basic Git Operations

Module 3: Branching and Merging

Module 4: Working with Remote Repositories

Module 5: Advanced Git Operations

Module 6: Git Tools and Techniques

Module 7: Collaboration and Workflow Strategies

Module 8: Git Best Practices and Tips

Module 9: Troubleshooting and Debugging

Module 10: Git in the Real World

© Copyright 2026. All rights reserved