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
- The scenario: Carla and the emergency
git stash: what it saves and what it does not- Untracked and ignored files:
-uand-a - The stack:
list,show,apply,pop,drop,clear applyversuspop- Referring to a specific entry
git stash push: messages, paths and interactive mode--keep-indexand--stagedgit stash branch: when the stash no longer fits- How it works underneath
- Risks: forgotten stashes and false backups
- 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.
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 |
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 popOn 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.
git stash: what it saves and what it does not
git stash: what it saves and what it does notgit 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
??ingit status). - Files ignored by
.gitignore.
This is the first trap, and it is a serious one. Had Carla run git stash without -u:
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:
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.
- Untracked and ignored files:
-u and -a
-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 -aOn -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:
Aliases are covered thoroughly in lesson 06-04, but this one is worth having straight away.
- The stack:
list, show, apply, pop, drop, clear
list, show, apply, pop, drop, clearThe stash is not a single drawer: it is a stack. You can set things aside several times, and each new entry goes on top.
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 teststash@{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:
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:
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.
apply versus pop
apply versus popBoth 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:
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 dropNote 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.
- Referring to a specific entry
Almost every command accepts an entry. If you leave it out, stash@{0} is used.
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:
In some shells the braces need quoting or escaping:
Since Git 2.11 the bare number is accepted too, which is more comfortable:
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.
git stash push: messages, paths and interactive mode
git stash push: messages, paths and interactive modegit 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:
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:
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 |
--keep-index and --staged
--keep-index and --stagedTwo 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 popIt 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 youpopafter the commit, those changes are already committed and can conflict. In practice it is usually combined with--include-untrackedand you accept that the laterpopsometimes calls for a mental--skip: check withgit stash show -pbefore 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
git stash branch: when the stash no longer fits
git stash branch: when the stash no longer fitsA 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:
This command does four things at once:
- Creates a new branch at the commit the stash was saved on.
- Switches to it.
- Applies the stash (which fits perfectly, because the context is the original one).
- 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.
- 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:
An ordinary commit. Let us look inside it with the same tools as in 01-04:
tree 3f8b1c7e2d9a5b4f6c1e8a3d7b2f5c9e4a1d6b8f parent 7d3a8f4c9b1e5d2a8f7c3b6e9d4a1c8f5b2e7d3a parent 8e2c5f1a9d3b7e4c1f6a8d2b5e9c3f7a4d1b8e6c parent 1c9e4b7f2a8d5c3e6b1f9a4d7c2e5b8f3a6d1c9e author Carla Vidal <carla.vidal@example.com> 1753959200 +0200 committer Carla Vidal <carla.vidal@example.com> 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:
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 testThat 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
- 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:
- It is local. It is never sent with
git push. The default refspec only coversrefs/heads/*(lesson 04-05), andrefs/stashfalls outside it. If your disk dies, the stash dies with it. - It is not cloned.
git clonedoes not bring anybody's stashes. Not evengit clone --mirrorreplicates them usefully. - It is fragile.
git stash cleardeletes 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:
- Modify a tracked file and create a new one without adding it.
- Run
git stashwithout-uand check withgit statuswhat has happened to each. - Restore, and repeat the operation with
-u. - Demonstrate with
git stash show -p -uthat 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:
- Set aside what does not belong in the commit.
- Check that the file is valid (
node --checkor similar). - Commit.
- 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:
- That
refs/stashpoints at a commit. - That the commit has three parents.
- What each of the three contains.
- That
git reflog stashandgit stash listshow 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 --shorttracked.txt has gone back to its original version; untracked.txt is exactly where it was. It has not been saved.
Now it works: the working tree really is clean and the new file has gone (it is in the stash).
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 newSolution 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(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.jsThe file contains only what was staged.
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"parent 4b8e1c7f2a9d5e3b6c1f8a4d7b2e5c9f3a6d1b8e parent 9d2f6a3c8b1e5f7d4a2c9e6b3f8d1a5c7e4b2f9d parent 6c1a8f4d3e7b2c5a9f1d6b8e3c7a4f2d5b9e1c8a
# 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.txtThe 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 listenumerates 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. popapplies and removes;applyapplies and keeps. If there is a conflict,popkeeps the entry and you have todropit by hand after resolving.--keep-indexleaves in the working tree only what you were about to commit (so that you can genuinely test it);--stagedsets aside only what is staged. Andpush -plets you choose hunk by hunk, andpush <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 originalHEAD, the index and the untracked files as parents, and the stack is that reference's reflog. That is where thestash@{N}syntax and the renumbering come from. - It is not a backup: it is local, it is not pushed, it is not cloned and
cleardeletes 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
- What Is Git?
- Installing Git
- Basic Git Terminology
- The Git Data Model
- Configuring Git
- Initial Configuration
Module 2: Basic Git Operations
- Creating a Repository
- Cloning a Repository
- The Basic Git Workflow
- Staging and Committing Changes
- Inspecting Changes with git diff
- Viewing Commit History
Module 3: Branching and Merging
- Understanding Branches
- Creating and Switching Branches
- Merging Branches
- Merge Strategies
- Resolving Merge Conflicts
- Branch Management
Module 4: Working with Remote Repositories
- Understanding Remote Repositories
- Adding a Remote Repository
- Authenticating with Remote Repositories
- Fetching and Pulling Changes
- Pushing Changes
- Tracking Branches
Module 5: Advanced Git Operations
Module 6: Git Tools and Techniques
- Using Git Hooks
- Git Bisect
- Git Blame
- Git Log and Aliases
- Git Submodules
- Multiple Working Copies with git worktree
Module 7: Collaboration and Workflow Strategies
- Forks and Pull Requests
- Code Reviews with Git
- The Git Flow Workflow
- GitHub Flow
- Trunk Based Development
- Continuous Integration with Git
Module 8: Git Best Practices and Tips
- Writing Good Commit Messages
- Keeping a Clean History
- Ignoring Files with .gitignore
- File Attributes with .gitattributes
- Security Best Practices
- Performance Tips
Module 9: Troubleshooting and Debugging
- Common Git Problems
- Undoing Changes
- Resolving Divergence with the Remote
- Recovering Lost Commits
- Dealing with Corrupted Repositories
- Advanced Debugging Techniques
