Home iOS & Swift Books Advanced Git

3
Stashes Written by Chris Belanger

After you’ve worked with Git for some time, you might start to feel a bit constrained. Sure, the staging, committing, branching and merging bits are all well and good, and these features undoubtedly support the nonlinear here-there-and-everywhere process of modern, iterative development. But the commit is still the atomic level of Git; there’s nothing below it.

It can begin to feel like Git is forcing you into a workflow wherein you can’t do anything outside of a commit, and that you have to limit yourself to building a working, documented commit each and every time you want to push your work to the repository. Talk about performance anxiety!

But Git recognizes that you can’t always work to the level of a commit. Development is messy and unpredictable; you may go down a few parallel rabbit holes, chasing different possibilities, before finding the right solution for a bug. Or, quite commonly, you may be building out the next great feature for your app when, suddenly, you need to switch over to a bugfix branch and get a hotfix out the door in an hour. Codus interruptus, for sure.

So in situations like the above, what do you do with that unfinished and uncompilable code that isn’t quite ready to be committed, but that you don’t want to lose?

Well, if you’re a paranoid coder like me, you may have developed a past habit of duplicating code directories that you want to keep for later, like so:

book-repo
├──git-book-before-edits
├──git-book-old
├──git-book-old-epub-test
├──git-book-older
└──git-book-version-with-jokes-removed

And if you bring that same thought process to working with Git, you’ll often want to do a similar thing by creating “interim commits” in the branch you’re working on, just so that you don’t lose your work inside the confines of the Git repository:

| * b10bc04 It’s the finalllllllll countdooooooooown do-do-do-doooooooooooo
| * 5f20836 Minor tweak
| * 36ff9ca fix lldb thing from errata forums
| * 0ce8469 Moved Mach-O section to DO_NOT_ADD_THIS… will keep it around for next version
| * 79c465d removed TODO
| * cebc416 ok, time to wrap this crap up, Derek
| * 42d40dd wow that sucked
| * e7e8228 omg apple changing everything internally
| * 6d162de Partial Revert "wooooooooooooot done w/ code signing"
| * 9676642 wooooooooooooot done w/ code signing
| * 986e288 ok, hopefully no more bugs on that script….
| * da5e21f Fix xattr bug
| * 52a2bc7 dsresign really looking good now
| * 6382f56 more tweaks
| * 66f0041 dsresign close to complete?
| * ba0bcf9 Added dsresign
| * 5ac42d4 meh
| * a5f08df Pre-delete a big section
| * 1d86a71 wow…. much words. Time to delete some content
.
.
.

(With apologies to the wonderful debugging guru Derek Selander at https://github.com/DerekSelander. Love ya, Derek.)

You can see how making interim commits to “save” your work can quickly get out of hand. It’s clear that there must be a better way to quickly stash work in progress and retrieve it later, when you’re done coding that duct-tape hotfix and are ready to return to refactoring the mess of code you left on a feature branch.

In fact, Git provides this feature out of the box, and it’s called, unsurprisingly, git stash.

Introducing git stash

Let’s return to your tightly knit team of Will, Xanthe, Yasmin and Zach. Things have been running smoothly for today, and you’ve been able to get some time to work on that all-important README file.

In the magicSquareJS repo, open README.md and add a bit of text to the bottom of the file:

## Contributing

To contribute to this project, simply

But before you can complete that thought, Xanthe pings you. “Can you take a quick look at the xUtils branch?” she asks. And, being the responsive, agile team member you are, you agree to have a look.

But you don’t want to lose that work you’ve done. Granted, it’s not a lot, but it does capture the state of your thoughts at that moment, and you don’t really want to redo that work. So save your work to README.md and exit out of your text editor.

Back at your command prompt, switch to the xUtils branch:

git checkout xUtils

Git detects that you have changes in your working tree that haven’t been captured anywhere, not at least as far as Git’s concerned:

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

Again, Git nudges you in the right direction, here: Either stash your changes or commit them before you move on. And although your reflex might be to commit them — because they’d be safe, you know — stashing your changes is a much better option.

Note: You will not get this error message if you did not complete the challenge from the last chapter.

How stashing works

In a manner that’s quite similar to the way you created all of those “backup” directories of your code projects in the past, Git lets you take your current set of changes in your working tree and stash them in your local repository, to be retrieved later.

To see this in action, simply execute the following command:

git stash

Git will respond with a somewhat cryptic but reassuring message:

Saved working directory and index state WIP on master: 870aea1 Merge branch 'xReadmeUpdates'

It appears that Git has saved your current set of changes somewhere… but where? To see that, you’ll dig into the internals of Git once again.

From the command prompt, navigate into the .git/refs directory:

cd .git/refs/

Inside that directory, you’ll find a file named, simply, stash. Print the contents of that file out to the command line with the following command to inspect its contents:

cat stash

In my case, that file holds a single line, which is simply an object hash:

b6132364bdae71e8a5483e42584257aadbe49827

Just as you did before, you’re going to use this object hash to look up the metadata and associated content of this object. Execute the following command to get the details about this object, substituting the actual value of your own hash for mine:

git cat-file -p b61323

Git tells me the following:

tree 1a79fa3410189b40dccb6b36f7eda8725c768627
parent 870aea10aa51d9103e6f6e37217b2cd077dd22bb
parent 68cde8e6fde76e21a7a93637f0bf5dbc3b36c242
author Chris Belanger <chris@razeware.com> 1556018137 -0300
committer Chris Belanger <chris@razeware.com> 1556018137 -0300

WIP on master: 870aea1 Merge branch 'xReadmeUpdates'

Well, that looks suspiciously like a commit. And, in fact, Git uses the same basic mechanism to stash your changes as it does to commit them. But instead of treating this as a commit, Git tracks this as a stash object.

I’ll dive further into this stash, so feel free to try this out with your own metadata and hashes. I’ll look up the tree hash of my stash with the following:

git cat-file -p 1a79fa

Git shows me the snapshot of my working tree at the moment I stashed it:

100644 blob 7b378be30685f019b5aa49dfbdd6a1c67001d73c	.tools-version
100644 blob 28c0f4fd0553ffb10b0ead9cd9584a4d251b61c8	IGNORE_ME
100644 blob 9d47666971a2b201db4d89f0536d5766af389c7c	LICENSE
100644 blob 475ce3189a12efc860a75ab19d6e8f30533c723c	README.md
100644 blob feab599b6be0efb9d20ef20e749b3b8e70e8c69f	SECRETS
040000 tree d0a7cf32f8ee481267d545000ca99dc532ef0579	css
040000 tree 29a422c19251aeaeb907175e9b3219a9bed6c616	img
100644 blob 0ab31637631bfdf857b1e8ad0c2e2ae435db2352	index.html
040000 tree 3876f897b04b07c02dcbe321ad267b97d1db532f	js

And then I’ll display the actual contents of the README.md file with the following:

git cat-file -p 475ce318

Git shows me the contents of that file, with my most recent change at the bottom (output truncated for brevity):

.
.
.
This project is maintained by teamWYXZ:
- Will
- Yasmin
- Xanthe
- Zack

## Contact Info

For info on this project, please contact [Xanthe](mailto:xanthe@example.com).

## Contributing

To contribute to this project, simply

So, just as if I’d committed this file to the repo, I have a snapshot of my working tree at a particular point in time, but held as a stash, instead of as a commit.

That’s enough playing around inside the Git internals; head back to the root of your project folder with the following command:

cd ../..

Now that you understand a little about how Git stores your stash, you can move on to retrieving what you’ve stashed.

Retrieving stashes

You’ve stashed your changes so that you can check out Xanthe’s xUtils branch, so continue to do that now:

git checkout xUtils

Git doesn’t complain this time, as it sees that you’ve created a stash of your current working tree and there are no unstashed or uncommitted changes.

For the purposes of this exercise, you’ll assume that you’ve looked through Xanthe’s changes and given her some advice on what she should do. With that task out of the way, you can now return to your changes that you’d like to complete on the master branch.

Switch back to master with the following command:

git checkout master

And take a look at the contents of README.md with the following:

cat README.md

Look at the end of the file, and you’ll see that your changes aren’t there. That makes sense, since Git switches you back to whatever the current state of master is on your local system. But how do you find your stashed changes and get them back into your working tree, since it’s already been a long day and there’s no way you’re going to remember the hash of the stash you created earlier?

Listing stashes

Git lets you create more than one stash, and it keeps them in a stack structure so you can easily find the one you want to apply. But, first, you’ll create another stash to illustrate this.

Imagine you don’t think you need that SECRETS file lying around anymore, but you want to test this later to make sure you really don’t need it. Delete that file from your working tree with the following:

rm SECRETS

And now create another stash, with the same command you used before:

git stash

Git gives you the same message as before; note that there’s no mention from Git of how to identify your most recent stash.

Saved working directory and index state WIP on master: 870aea1 Merge branch 'xReadmeUpdates'

Note: Since you’re working with a stash, it seems that Git should let you use common stack operations, and, in fact, it does. git stash is a convenience alias for git stash push, for quickly creating a non-named stash on the stack. You also have access to common stack operators such as pop, show and list, as you’ll see in the following sections.

To see the stack of stashes, use the list option on git stash:

git stash list

Git shows you all of the stashes it knows about:

stash@{0}: WIP on master: 870aea1 Merge branch 'xReadmeUpdates'
stash@{1}: WIP on master: 870aea1 Merge branch 'xReadmeUpdates'

Since this is a stack, the stash at index 0, denoted by the stash@{0} label, is the most recent, with the stash at index 1, being older as it’s farther down the stack.

Now, the default stash message really isn’t that descriptive; if you only work with one stash at a time, that might be sufficient. But you won’t always remember what the most recent stash is, if they all have the same stash message. But just as you can provide a message when you create a commit, you can specify a message when you create a stash.

Adding messages to stashes

Make another change in your project working tree, and create a temporary file with the following stacked commands:

mkdir temp && touch temp/.keep && git add .

Note: The && operator in Bash lets you chain commands and execute them in order, but only if the preceding command succeeded (that is, had an exit code of 0). So, in this case, you’re trying to create a directory named temp; if that succeeds, you then create a .keep file in the temp directory so that Git recognizes this directory. If the file creation was successful, then you call git add . to stage this file so that Git can track it.

Now that you have a tracked addition to your working tree, you can create a stash with an appropriate message to help you keep track of what’s what. Execute the following command:

git stash push -m "Created temp directory"

In this case, you’ve used the push operator, since git stash alone doesn’t let you supply any arguments. Now, pull up the stash stack again with git stash list to see what the stack looks like now:

stash@{0}: On master: Created temp directory
stash@{1}: WIP on master: 870aea1 Merge branch 'xReadmeUpdates'
stash@{2}: WIP on master: 870aea1 Merge branch 'xReadmeUpdates'

That’s a little more instructive, but your memory is short. If you can’t recall what exactly you did in a particular stash, you can peek at the contents of that stack entry with the following command:

git stash show stash@{0}

Git tells you briefly what the changes are in this stashed snapshot:

 temp/.keep | 0
 1 file changed, 0 insertions(+), 0 deletions(-)

In fact, you can use most of the options from git log on git stash show, since Git is simply using its own log to show the changes contained in your snapshot. You can check out the diff, or patch, of your very first stash using the -p option as you would with git log:

git stash show -p stash@{2}

Git then shows you the entire patch of your stash. Here’s mine:

diff --git a/README.md b/README.md
index b7338e2..475ce31 100644
--- a/README.md
+++ b/README.md
@@ -18,3 +18,7 @@ This project is maintained by teamWYXZ:
 ## Contact Info

 For info on this project, please contact [Xanthe](mailto:xanthe@example.com).
+
+## Contributing
+
+To contribute to this project, simply

Here, you can see the final four lines added to the end of README.md. Speaking of which, you should probably finish those updates to the README file before you lose your creative inspiration.

Popping stashes

Now, you want to get back to the state represented by the stash at the bottom of your stack: stash@{2}. If you remember your stack operations from your data structures and algorithms courses, you know that you’d generally pop something off the top of this list. And in fact, you can do this with Git, using git stash pop to remove the top stash from the stack and apply that patch to your working environment.

In this case, however, the stash you want isn’t at the top; rather, it’s at the bottom of the stack. And you’ve decided that you don’t really want to keep those other two stashes around, either.

Git lets you cherry-pick a particular stash out of the stack and apply it to your working tree. So to get back to the state where you created that first stash, that is, with your modification to the README file, first reset the state of your working tree, to get rid of any local changes:

git reset --hard HEAD

Now, you can use git stash apply to apply a particular stash to your working tree. In this case, you want the stash at the bottom of the stack — stash@{2}:

git stash apply stash@{2}

Git then reverts the state of your working tree to the snapshot captured in the stash. In this case, your stash doesn’t have the temp directory, and it still has the SECRETS file, as you deleted that file after you created the stash. Pulling up a simple directory listing with ls -la will show you this:

drwxr-xr-x  12 chrisbelanger  staff    384 23 Apr 10:15 .
drwxr-xr-x   8 chrisbelanger  staff    256 16 Apr 05:42 ..
drwxr-xr-x  15 chrisbelanger  staff    480 23 Apr 10:16 .git
-rw-r--r--   1 chrisbelanger  staff      5 16 Apr 16:18 .tools-version
-rw-r--r--   1 chrisbelanger  staff     43 16 Apr 16:18 IGNORE_ME
-rw-r--r--   1 chrisbelanger  staff  11343 16 Apr 05:42 LICENSE
-rw-r--r--   1 chrisbelanger  staff    538 23 Apr 10:15 README.md
-rw-r--r--   1 chrisbelanger  staff     65 23 Apr 08:47 SECRETS
drwxr-xr-x   5 chrisbelanger  staff    160 23 Apr 08:39 css
drwxr-xr-x   3 chrisbelanger  staff     96 16 Apr 05:42 img
-rw-r--r--   1 chrisbelanger  staff   1259 23 Apr 08:39 index.html
drwxr-xr-x   6 chrisbelanger  staff    192 23 Apr 08:39 js

Now, execute git status to see how Git interprets the situation, and it notes that you have one change not staged for commit:

On branch master
Your branch is ahead of 'origin/master' by 17 commits.
  (use "git push" to publish your local commits)

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

	modified:   README.md

no changes added to commit (use "git add" and/or "git commit -a")

If you think about it, this makes sense; you didn’t stage that change, so Git’s snapshot of the working tree in your stash also represents Git’s tracking status of files, whether they were staged or unstaged. And when you apply a stash, you overwrite not just the working tree, but also the staging area of your repository. Remember: You staged your addition of the temp/.keep file after you created the stash you just applied.

Applying stashes

Applying a stash doesn’t remove it from the stack; you can see this if you execute git stash list:

stash@{0}: On master: Created temp directory
stash@{1}: WIP on master: 870aea1 Merge branch 'xReadmeUpdates'
stash@{2}: WIP on master: 870aea1 Merge branch 'xReadmeUpdates'

stash@{2} is still sitting at the bottom of the stack; Git simply applied the patch resulting from this change and left that stash on the stack. In just a bit, you’ll see how to remove elements from this stack.

Open up README.md in an editor and finish off that excellent line of documentation that you spent all night dreaming up:

## Contributing

To contribute to this project, simply create a pull request on this repository and we’ll review it when we can.

Save your changes, and exit the editor. Now, you can stage and commit your manifesto, again using the stacking option of && in Bash to do it all on one line:

git add . && git commit -m "Updates readme"

#lazygit for the win, kids! Oh, but wait. You really did want to add that temp/.keep file after all. Fortunately, that change was stashed at the top of the stash stack, so you can use the pop operator to simultaneously apply the patch of that stash and remove it from the top of the stack:

git stash pop

Git gives you a nice status message, combining the output you might expect from git status along with a little note at the end telling you which stash was popped from the stack:

On branch master
Your branch is ahead of 'origin/master' by 18 commits.
  (use "git push" to publish your local commits)

Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

	new file:   temp/.keep

Dropped refs/stash@{0} (fca102bbfc2e4dc51264ae46211f95164ac2c933)

You’ve applied that patch on top of your working tree, and because Git stashed the tracking information as well as the state of the files in your working tree, those changes have already been staged for commit.

Clearing your stash stack

Now, you might think you’re done here, but you still have a few stashes hanging around that you don’t really need. Use git stash list to see what’s still hanging around in the stash stack:

stash@{0}: WIP on master: 870aea1 Merge branch 'xReadmeUpdates'
stash@{1}: WIP on master: 870aea1 Merge branch 'xReadmeUpdates'

You can see that Git popped the top of the stack (the On master: Created temp directory entry), so it’s gone. But you can easily get rid of all entries with the clear option.

In your case, you don’t want any of the stash entries. So execute the following command to clear the entire stash stack:

git stash clear

Execute git stash list now, and you’ll see that there are no more stashes hanging around.

Merge conflicts with stashes

Since applying stashes looks a lot like merging commits from Git’s perspective, you may be wondering if you can also get conflicts when merging a stash. Well, maybe you weren’t wondering this, but I’ll bet you are now!

The answer is yes — you can certainly experience merge conflicts when working with stashes. You’ll set up a scenario now that will show you how to resolve a merge conflict with a stash.

Recall that a merge conflict occurs when Git detects a change to a common subsequence inside a file. In your case, the best way to illustrate this is to alter the same line in the README file between a branch and a stash.

You’re currently on master, so create a small edit to README.md to keep this author happy with proper use of English. Change the shorthand term “info” to “information” as shown below:

For information on this project, please contact [Xanthe](mailto:xanthe@example.com).

Save your work, exit the editor, and add this change as a stash using the following command:

git stash

Now, switch to the xUtils branch, where you’ll create a conflicting change:

git checkout xUtils

Open README.md in that branch and add the following line, immediately after the # Maintainers section:

## Contact Info

For info on this project, please contact [Xanthe](mailto:xanthe@example.com) or [Will](mailto:will@example.com).

Here, you’ve added Will as a contributor, and you’ll notice that this line still has the shorthand “info” in use.

Save your work and exit the editor. Now, stage and commit those changes — remember, you can’t merge into a “dirty” or uncommitted branch:

git add . && git commit -m "Added Will as a contributor"

Now attempt to apply the stash with the following command:

git stash pop

Git responds with a message noting the conflict:

Auto-merging README.md
CONFLICT (content): Merge conflict in README.md

Open README.md in an editor, and edit the conflict as you’ve done before so that the final section looks like the following:

## Contact Info

For information on this project, please contact [Xanthe](mailto:xanthe@example.com) or [Will](mailto:will@example.com).

Save your changes and exit the editor. Now you can commit those changes to the xUtils branch:

git add . && git commit -m "Corrected language usage"

Challenge: Clean up the remaining stash

Is there anything left to do? Execute git stash list and you’ll see there’s still a stash there. But you popped that stash, didn’t you? Of course you did.

Your challenge is twofold, and may require a little dive into the Git documentation:

  1. Explain why there’s still a stash in the stack, even though you executed git stash pop.
  2. Remove the remaining entry from the stash without using git stash pop or git stash clear. There’s one more way to remove entries from the stack with git stash — what is it?

If you get stuck, or want to check your solution, you can always find the answer to this challenge under the challenge folder for this chapter.

Key points

  • Stashing lets you store your in-progress work and retrieve it later, without committing it to a branch.
  • git stash takes a snapshot of the current state of your local changes and saves it as a stash.
  • Git stores stashes as a stack; all successive stashes are pushed on the top of the stack.
  • Git stashes work much like commits do, and it is captured inside the .git subdirectory.
  • git stash is a convenience alias for git stash push.
  • git stash list shows you the stack of stashes you’ve made.
  • git stash show <stashname> reveals a brief summary of the changes inside a particular stash.
  • git stash show -p <stashname> shows the patch, or diff, of the changes in a particular stash.
  • git stash apply <stashname> merges the patch of a stash to your working environment.
  • git stash pop pops the top stash off of the stack and merges it with your working environment.
  • Merging a stash can definitely result in conflicts, if there are committed changes that touch the same piece of work. Resolve these conflicts in the same way as you would when merging two branches.

Where to go from here?

Stashes are an excellent Git mechanism that help make your life as a developer just a little bit easier. And what’s nice is that, again, Git follows common workflows that come naturally to most developers, such as stashing not-quite-done-yet changes off to the side (which is an improvement over those terrible copies of directories I used to make).

Not everything in life goes perfectly, though, and quite often you’ll want to be able to fix things when they go awry (and in Git, they often do). The next chapter deals with a very common scenario that will (and should) occur with some regularity if you’re doing any level of distributed development: merge conflicts.

Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.

Have feedback to share about the online reading experience? If you have feedback about the UI, UX, highlighting, or other features of our online readers, you can send them to the design team with the form below:

© 2020 Razeware LLC