Embercode
✕
Git and GitHubLesson 9 of 16

35 min read

Stashing unfinished work

Why this matters

You are halfway through a change. Three files are edited, and one paragraph is only half written. Then someone asks you to fix a typo on another branch, right now. You try to switch branches, and Git refuses because your edits would be overwritten by the switch.

You have three bad options. You can commit the half-written work, which puts unfinished code into the project history. You can copy the files to a folder by hand, and then forget one of them. Or you can throw the edits away, which loses the work.

git stash gives you a fourth option. It takes your unfinished changes off the project, puts them on a shelf, and leaves the project exactly as it was at your last commit. Later you take the changes off the shelf and carry on.

This lesson was checked with Git 2.53.0. Branches are covered in the lesson "Branches and merging", so this lesson only uses them where stashing needs them.

The idea: a labelled shelf

Think of a cook who is making a sauce when the doorbell rings. The cook puts the pan on a shelf with a sticky note that says "tomato sauce, half done", wipes the counter, and answers the door. When the cook is back, the pan goes back on the stove and the sauce carries on from where it stopped.

A stash works the same way. Each shelf item is called a stash entry. Each entry has a label, which is the branch you were on and the message you gave it.

Some words you need first:

  • A commit is a saved snapshot of the project, with a message that says what changed.
  • The working tree is the set of files you see and edit in your editor.
  • A tracked file is a file Git already knows about, because it was in an earlier commit or you added it with git add.
  • An untracked file is a new file that Git has never been told about.
  • The staging area (also called the index) is the list of changes you have chosen for the next commit, made with git add.

By default, a stash saves changes to tracked files and anything in the staging area. It does not save untracked files. The section on untracked files below shows how to stash them when you want to.

Set up a project to practise on

Create a small project for a cafe website:

mkdir cafe-site
cd cafe-site
git init -b main
git config user.name "Your Name"
git config user.email "you@example.com"
printf '<h1>Corner Cafe</h1>\n<p>Open every day.</p>\n' > index.html
git add index.html
git commit -m "Add home page"

git init -b main creates a new repository whose main line of work is called main. The two git config lines set the name and email that appear on your commits. Use your own. The printf command writes two lines into index.html. You can also create the file in your editor.

Check that the project is clean:

git status
On branch main
nothing to commit, working tree clean

"Working tree clean" means every file matches the last commit. Nothing is on the shelf yet.

Your first stash

Now make an edit you are not ready to commit. Add a footer line to index.html, using your editor or this command:

printf '<h1>Corner Cafe</h1>\n<p>Open every day.</p>\n<footer>Call us: 555-0100</footer>\n' > index.html

Check what Git sees:

git status
On branch main
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
	modified:   index.html
 
no changes added to commit (use "git add" and/or "git commit -a")

"Changes not staged for commit" means the file changed but you have not chosen it for a commit yet. Now put the change on the shelf and give it a label:

git stash push -m "half-done footer"
Saved working directory and index state On main: half-done footer

Run git status again and the working tree is clean, the same as the start of this lesson. The footer is no longer in index.html. Check that:

cat index.html
<h1>Corner Cafe</h1>
<p>Open every day.</p>

The shelf now holds one item. List it:

git stash list
stash@{0}: On main: half-done footer

stash@{0} is the name of the entry. Git numbers entries from newest to oldest, so stash@{0} is always the most recent one. The text after the colon shows the branch and your label.

To bring the change back, use pop:

git stash pop
On branch main
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
	modified:   index.html
 
no changes added to commit (use "git add" and/or "git commit -a")
Dropped refs/stash@{0} (877eaf351753a53a815d29a2ce33d2d18e3f8075)

pop does two things. It applies the stash to the working tree, and it deletes the entry from the list, as the line Dropped refs/stash@{0} shows. The hash in brackets is the stash's internal ID. Your hash will be different.

Apply keeps the entry

git stash apply does the same work as pop but leaves the entry on the shelf. Use it when you want to try the stash in more than one place, or when you are not sure the stash applies cleanly and want a copy to fall back on.

CommandChanges come backEntry stays in the list
git stash popYesNo, it is removed on success
git stash applyYesYes

If you run git stash apply and then decide the entry is no longer needed, delete it with git stash drop.

Two entries and picking one

Each push adds a new entry at the top of the list, so the list can hold several items. Here is a clean run that makes two entries. First, the footer again:

printf '<h1>Corner Cafe</h1>\n<p>Open every day.</p>\n<footer>Call us: 555-0100</footer>\n' > index.html
git stash push -m "footer"
Saved working directory and index state On main: footer

Then a second change, the opening hours:

printf '<h1>Corner Cafe</h1>\n<p>Open 7am to 9pm.</p>\n' > index.html
git stash push -m "hours"
git stash list
Saved working directory and index state On main: hours
stash@{0}: On main: hours
stash@{1}: On main: footer

A plain git stash pop takes stash@{0}, the newest entry. To take the older one, name it:

git restore index.html
git stash pop stash@{1}
On branch main
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
	modified:   index.html
 
no changes added to commit (use "git add" and/or "git commit -a")
Dropped stash@{1} (163279a0c9974792c80a6f84612674a76b453d94)

git restore index.html throws away the current edit to the file, so the pop of the older entry starts from a clean file. Only the footer came back, and the hours entry is still on the shelf:

git stash list
stash@{0}: On main: hours

Note that git restore is destructive. It removes changes that are in the working tree. Stash first if you are not sure you want to lose them.

To delete an entry you no longer need:

git stash drop
Dropped refs/stash@{0} (d963da64af1173808f10647f9e94a35b505ed73e)

Running git stash pop now shows what happens when the list is empty:

No stash entries found.

Switching branches with unfinished work

This is the situation that started the lesson. Git has a rule for switching branches: it refuses to switch if the switch would overwrite a change you have not committed or stashed. Git does not refuse if the file you changed is the same on both branches. In that case the change comes along.

Create a second branch with a new menu page. Use git switch -c, which creates a branch and moves onto it:

git switch -c add-menu
printf '<h1>Menu</h1>\n<p>Coffee, tea, toast.</p>\n' > menu.html
git add menu.html
git commit -m "Add menu page"
git switch main

Back on main, edit index.html but do not commit it. Then switch to add-menu:

git switch add-menu
Switched to branch 'add-menu'
M	index.html

The switch worked. The line M index.html tells you the uncommitted change came along, because add-menu has the same index.html as main. You can check which branch you are on and what is changed:

git status --short
git branch
 M index.html
* add-menu
  main

The M in front of the filename means modified. The star in front of add-menu marks the branch you are on.

When the switch is blocked

Now a case where the file differs. Make a branch that changes the opening hours and commit it:

git switch main
git switch -c update-hours
printf '<h1>Corner Cafe</h1>\n<p>Open 7am to 9pm.</p>\n<footer>Call us: 555-0100</footer>\n' > index.html
git commit -am "Change opening hours"
git switch main

Now go back to main and edit the same line in a different way, without committing:

printf '<h1>Corner Cafe</h1>\n<p>Open daily, 8am to 8pm.</p>\n<footer>Call us: 555-0100</footer>\n' > index.html
git switch update-hours
error: Your local changes to the following files would be overwritten by checkout:
	index.html
Please commit your changes or stash them before you switch branches.
Aborting

Git stopped before it changed anything. Check the status to confirm:

git status --short
git branch --show-current
 M index.html
main

Your edit is still there, and you are still on main. The error message gives you two choices: commit the change, or stash it. For unfinished work, stash it:

git stash push -m "new hours wording"
git switch update-hours
Saved working directory and index state On main: new hours wording
Switched to branch 'update-hours'

You can look at what a stash contains before you bring it back:

git stash show -p stash@{0}
diff --git a/index.html b/index.html
index b4188ab..158086c 100644
--- a/index.html
+++ b/index.html
@@ -1,2 +1,3 @@
 <h1>Corner Cafe</h1>
-<p>Open every day.</p>
+<p>Open daily, 8am to 8pm.</p>
+<footer>Call us: 555-0100</footer>

The -p flag means "show the patch", the list of lines removed (starting with -) and added (starting with +). Lines that start with a space did not change and are shown for context.

What a conflict on pop looks like

Here the stash was made on main, but you are now on update-hours, which already changed the same line. Putting the stash back on top of that line is a conflict. A conflict happens when Git cannot decide which version of a line to keep, so it asks you to choose.

git stash pop
Auto-merging index.html
CONFLICT (content): Merge conflict in index.html
On branch update-hours
Unmerged paths:
  (use "git restore --staged <file>..." to unstage)
  (use "git add <file>..." to mark resolution)
	both modified:   index.html
 
no changes added to commit (use "git add" and/or "git commit -a")
The stash entry is kept in case you need it again.

Open index.html. Git has marked both versions inside the file:

<h1>Corner Cafe</h1>
<<<<<<< Updated upstream
<p>Open 7am to 9pm.</p>
=======
<p>Open daily, 8am to 8pm.</p>
>>>>>>> Stashed changes
<footer>Call us: 555-0100</footer>

The part between <<<<<<< and ======= is the version on the branch you are on. The part between ======= and >>>>>>> is the version from your stash. To finish, edit the file so it has the text you want, with the marker lines removed, then run git add index.html. Because the pop did not finish, the stash entry is still on the list. Remove it with git stash drop after you are sure the result is right.

If you would rather back out, the undo is two commands. git restore --staged removes the file from the staging area, and git restore throws away the conflicted version:

git restore --staged index.html
git restore index.html
git status --short

Git prints nothing for git status --short when the tree is clean. The stash entry is still there, so nothing is lost.

Stashing new files with -u

A stash does not include untracked files by default. Make a new file and try to stash it:

printf 'Latte 4.50\n' > prices.txt
git stash push -m "prices draft"
No local changes to save

Git saw nothing to save and did not create an entry. The file is still in your folder, and git status --short prints ?? prices.txt to show it is untracked. The ?? marks an untracked file.

To include untracked files, add -u, which means --include-untracked:

git stash push -u -m "prices draft"
git status --short
git stash list
Saved working directory and index state On update-hours: prices draft
stash@{0}: On update-hours: prices draft
stash@{1}: On main: new hours wording

Now git status --short prints nothing, and the file is in the stash. Bring it back with git stash pop:

git stash pop
Already up to date.
On branch update-hours
Untracked files:
  (use "git add <file>..." to include in what will be committed)
	prices.txt
 
nothing added to commit but untracked files present (use "git add" to track)
Dropped refs/stash@{0} (b9c199dc3546e205cdbd5802296a72e05efa76c0)

The line Already up to date. comes from an internal step in the pop. It does not mean the file was missed. The file is back, and the entry is dropped.

Stashing one file

Sometimes you want to put only some of your changes away. Add a footer to index.html and a line to prices.txt, then stash only index.html. The path after -- names the file:

printf '<footer>Call us: 555-0100</footer>\n' >> index.html
printf 'Tea 2.00\n' >> prices.txt
git stash push -m "index only" -- index.html
git status --short
git stash list
Saved working directory and index state On update-hours: index only
?? prices.txt
stash@{0}: On update-hours: index only
stash@{1}: On main: new hours wording

The prices.txt file stays in your working folder, and index.html is on the shelf. This is useful when your work is two unrelated changes and you want to switch to something else without touching the second one.

Common mistakes

Expecting a stash to save everything. A stash does not save new files unless you use -u. Run git status --short before you stash and check for lines that start with ??.

Expecting pop to take a specific entry. pop takes the newest entry, stash@{0}. If you want an older one, name it, as in git stash pop stash@{1}. Look at git stash list first.

Treating a stash as a backup. A stash lives only in your local copy of the repository. git push does not send stashes to the remote, so if your computer is lost, the stash is lost too. Use a branch and a commit for work you need to keep.

Leaving stashes behind. A list of old entries with the same label is hard to read. Give every stash a message with -m, and drop entries you no longer need.

Stashing and then forgetting the entry. The change is safe on the shelf, but nothing reminds you it is there. Check git stash list before you finish a session.

Running pop on an empty list. The message No stash entries found. means there is nothing to bring back. It is not an error in your files. Check the list to see what you have.

Switching with an untracked file in the way. If the branch you switch to has a file with the same name as an untracked file in your folder, Git stops:

error: The following untracked working tree files would be overwritten by checkout:
	menu.html
Please move or remove them before you switch branches.
Aborting

Move the file to another folder, or stash it with -u, and then switch.

Practice

Exercise 1. You changed index.html and want to check out main to read something. Write the commands to put the change on the shelf with the label "menu wording", switch to main, and bring the change back later.

Show the answer

Run git stash push -m "menu wording", then git switch main. When you are ready to go back, switch to your branch with git switch <branch name> and run git stash pop. Check git stash list first if you have more than one entry, so you pop the right one.

Exercise 2. You created a new file called specials.txt and modified index.html. You want to stash both. Which command stashes both, and what does git status --short show afterwards?

Show the answer

Run git stash push -u -m "specials and footer". The -u flag includes the untracked file specials.txt. Afterwards, git status --short prints nothing, because both changes are on the shelf.

Exercise 3. The list shows three entries, and you want the second oldest. What is the name of that entry, and what command brings it back and removes it from the list?

Show the answer

The list is numbered from the newest, so the entries are stash@{0}, stash@{1} and stash@{2}. The second oldest is stash@{1}. Run git stash pop stash@{1}. Pop removes the entry after it applies it without a conflict. If you use git stash apply stash@{1} instead, the entry stays on the list.

Exercise 4. You ran git stash pop and the output says CONFLICT (content): Merge conflict in index.html. Describe what is on your disk now, and what you do to finish.

Show the answer

index.html has conflict markers between <<<<<<<, ======= and >>>>>>>, and the stash entry is still on the list. Edit the file so it reads the way you want, remove the marker lines, run git add index.html, and then run git stash drop to delete the entry. If you want to start over, run git restore --staged index.html and then git restore index.html.

Exercise 5. You have uncommitted changes to index.html, and git switch feature fails with Your local changes to the following files would be overwritten by checkout. Write the full sequence to switch, do a short piece of work on feature, and return to your change.

Show the answer

Run git stash push -m "work in progress", then git switch feature. Do the short piece of work and commit it, then run git switch main (or whichever branch you came from). Run git stash pop to bring the change back. If the file changed on both branches, the pop can stop with a conflict, which you resolve as in exercise 4.

Summary

  • git stash push -m "label" moves your uncommitted changes onto a shelf and leaves a clean working tree.
  • git stash list shows the entries, newest first, as stash@{0}, stash@{1} and so on.
  • git stash pop brings back the newest entry (or the one you name) and removes it on success. git stash apply brings it back and keeps the entry.
  • A stash does not include new untracked files unless you add -u, and -- <file> limits a stash to one file.
  • Git refuses a branch switch that would overwrite your changes, so stash first and pop after you return.
  • A stash is local only. git push does not send it, so commit anything you need to keep.
Check yourself: Stash (6 questions) →

Stuck? Ask the tutor

Ask about anything in this lesson. Answers are short and use simple words.

Sign in to ask the tutor.