Section 4 · Professional Habits
Stash and .gitignore
15 min4 min readSpot the bug
Lesson at a glance
Park unfinished work with git stash and bring it back later, and write a .gitignore that keeps dependencies, build output, secrets and OS clutter out of your repository.
- 1Stash: a drawer for unfinished work
- 2.gitignore: files Git should never track
- 3How patterns work
- 4Ignoring doesn’t untrack
No video here — just the real thing: a written lesson, runnable examples, a quiz and practice.
Two small tools keep a working tree under control. git stash gives you a drawer for work that isn’t ready to commit. .gitignore tells Git which files it should never track at all. Both prevent the same kind of accident: the wrong thing ending up in a commit.
Stash: a drawer for unfinished work
You’re halfway through the portfolio’s contact form on the contact-form branch when someone points out a typo on the live home page. You need main, now. But git switch main refuses, because your edits to index.html would be overwritten, and you don’t want to commit a half-built form.
Stash it:
git stash push -m "contact form: fields done, validation next"
git status # nothing to commit, working tree cleanYour changes, staged and unstaged, are saved in a stash entry and removed from the working tree. Now fix the typo:
git switch main
# fix, commit, push
git switch contact-form
git stash poppop reapplies the most recent stash and deletes it. You’re back exactly where you were.
The other stash commands you’ll use:
git stash list # stash@{0}: On contact-form: contact form: fields done…
git stash show -p stash@{0} # the diff inside a stash
git stash apply stash@{1} # reapply but keep the stash
git stash drop stash@{1} # delete one stash
git stash push -u -m "…" # include untracked (new) files tooThat last flag matters. By default stash only saves changes to tracked files. A brand-new contact.js stays in your folder, untouched, which surprises people when they pop and it isn’t “restored”. Use -u whenever you’ve created files.
Stashes aren’t tied to a branch: you can pop one onto any branch, which is occasionally handy and occasionally a surprise. If you realise the stashed work deserves its own branch after all, git stash branch contact-validation creates a branch at the commit where you stashed, applies the stash there and drops it, which sidesteps conflicts with anything that has changed since.
If pop hits a conflict, Git applies what it can, marks the conflict as usual, and keeps the stash instead of dropping it. Resolve, git add, and run git stash drop once you’re sure.
.gitignore: files Git should never track
Some files belong in your project folder but never in history:
- dependencies you can reinstall, such as
node_modules/; - build output you can regenerate, such as
dist/; - secrets and machine-specific settings, such as
.env; - clutter from your OS and editor, such as
.DS_Storeon macOS.
List them in a file called .gitignore at the repository root and commit it, so everyone on the project shares the rules:
# Dependencies
node_modules/
# Build output
/dist/
# Local environment and secrets
.env
.env.*
!.env.example
# OS and editor files
.DS_Store
*.logIgnored files disappear from git status and git add . skips them.
How patterns work
Each line is a pattern, matched against paths relative to the .gitignore file’s folder:
#starts a comment, but only at the beginning of a line. Blank lines are ignored.*.logmatches any file ending in.log, in any folder.- A trailing
/matches directories only:node_modules/. - A leading
/anchors the pattern to this folder:/dist/ignores the rootdistbut notsrc/dist. **/matches any depth:**/fixtures/*.json.!re-includes something an earlier line excluded:!.env.examplekeeps the template for other developers. It can’t re-include a file whose parent folder is excluded, because Git never looks inside an ignored folder.
When a file is ignored and you don’t know why, ask Git:
git check-ignore -v dist/app.js.gitignore:5:/dist/ dist/app.jsIt prints the file, line number and pattern responsible.
For clutter that only your machine creates, don’t burden every project’s .gitignore. Git also reads a global ignore file, ~/.config/git/ignore, which is a good home for .DS_Store and editor folders. GitHub keeps a collection of starter .gitignore templates for most languages, and offers one when you create a repository.
Ignoring doesn’t untrack
.gitignore only affects untracked files. If .env was committed before you ignored it, Git keeps tracking it. Remove it from the index and commit, which keeps your local file:
git rm --cached .env
git commit -m "Stop tracking .env"Your working tree is now clean and your repository holds only what it should. Next you’ll make what it holds readable: commit messages, tags and releases.
Key takeaways
git stash push -m "…"saves your uncommitted changes and cleans the working tree;git stash popbrings them back and removes the stash.git stashskips untracked files unless you add-u(--include-untracked)..gitignoreonly affects untracked files; to stop tracking a committed file, rungit rm --cachedand commit.- In
.gitignore, a trailing/matches directories only, a leading/anchors to that folder, and!re-includes a file. - A secret that was ever committed and pushed must be revoked and replaced, because history still contains it.