# Undoing and recovering
```{objectives}
- Learn to undo changes safely
- See when undone changes are permanently deleted and when they can be retrieved
```
```{instructor-note}
- 10 min teaching/type-along
- 15 min exercise
```
One of the main points of version control is that you can *go back in
time to recover*. Unlike this xkcd comic implies:
In this episode we show a couple of commands that can be used to undo mistakes.
We also list a couple of common mistakes and discuss how to recover from them.
Some commands preserve the commit history and some modify commit history.
Modifying history? Isn't a "commit" permanent?
- You can modify old commit history.
- But if you have shared that history already, *modifying it can make
a huge mess*.
```{note}
As long as you commit something once (or at least `git add` it), you
can almost always go back to it, no matter what you do.
But you may need to ask Stack Overflow or your local
guru... *until that guru becomes you*.
```
---
## Undoing your recent, uncommitted and unstaged changes (preserves history)
You do some work, and want to **undo your uncommitted and unstaged modifications**.
You can always do that with:
- `git restore .`: (the dot means "here and in all folders below")
Or, you can undo things selectively:
- `git restore -p` (decide which portions of changes to undo) or `git restore ` (decide which path/file)
```{note}
In case the above does not work, your Git version might be older than from 2019.
On older Git it is `git checkout` instead of `git restore`.
```
If you have staged changes, you have at least two options to undo the staging:
- `git restore --staged .` followed by `git status` and `git restore .`
- `git reset --hard HEAD` throws away everything that is not in last commit (`HEAD`)
---
## Reverting commits (preserves history)
- Imagine we made a few commits.
- We realize that the latest commit `f960dd3` was a mistake and we wish to undo it:
```console
$ git log --oneline
f960dd3 (HEAD -> master) not sure this is a good idea
dd4472c we should not forget to enjoy
2bb9bb4 add half an onion
2d79e7e adding ingredients and instructions
```
A safe way to undo the commit is to revert the commit with `git revert`:
```console
$ git revert f960dd3
```
This creates a **new commit** that does the opposite of the reverted commit.
The old commit remains in the history:
```console
$ git log --oneline
d62ad3e (HEAD -> master) Revert "not sure this is a good idea"
f960dd3 not sure this is a good idea
dd4472c we should not forget to enjoy
2bb9bb4 add half an onion
2d79e7e adding ingredients and instructions
```
You can revert any commit, no matter how old it is. It doesn't affect
other commits you have done since then - but if they touch the same
code, you may get a conflict (which we'll learn about later).
(exercise-revert)=
### Exercise: Revert a commit
```{exercise} Undoing-1: Revert a commit
- Create a commit (commit A).
- Revert the commit with `git revert` (commit B).
- Inspect the history with `git log --oneline`.
- Now try `git show` on both the reverted (commit A) and the newly created commit (commit B).
```
## Adding to the previous commit (modifies history)
Sometimes we commit but realize we forgot something.
We can amend to the last commit:
```console
$ git commit --amend
```
This can also be used to modify the last commit message.
Note that this **will change the commit hash**. This command **modifies the history**.
This means that we avoid this command on commits that we have shared with others.
(exercise-amend)=
### Exercise: Modify a previous commit
````{exercise} Undoing-2: Modify a previous commit
1. Make an incomplete change to the recipe or a typo in your change, `git
add` and `git commit` the incomplete/unsatisfactory change.
2. Inspect the unsatisfactory but committed change with `git show`. Remember
or write down the commit hash.
3. Now complete/fix the change but instead of creating a new commit, add the
correction to the previous commit with `git add`, followed by `git commit
--amend`. What changed?
```{solution}
One thing that has changed now is the commit hash. Modifying the previous
commit has changed the history. This is OK to do on commits that other people
don't depend on yet.
```
````
## Rewinding branches (modifies history)
You can **reset branch history** to move your branch back to some
point in the past.
* `git reset --hard ` will force a branch label to any other point. All
other changes are lost (but it is possible to recover if you force reset by mistake).
* Be careful if you do this - it can mess stuff up. Use `git graph` a
lot before and after.
(exercise-reset)=
### Exercise: Git reset
````{exercise} Undoing-3: Destroy our experimentation in this episode
After we have experimented with reverts and amending, let us destroy
all of that and get our repositories to a similar state.
- First, we will look at our history (`git log`/`git graph`) and
find the last commit `` before our tests.
- Then, we will `git reset --hard ` to that.
- Then, `git graph` again to see what happened.
```console
$ git log --oneline
d62ad3e (HEAD -> master) Revert "not sure this is a good idea"
f960dd3 not sure this is a good idea
dd4472c we should not forget to enjoy
2bb9bb4 add half an onion
2d79e7e adding ingredients and instructions
$ git reset --hard dd4472c
HEAD is now at dd4472c we should not forget to enjoy
$ git log --oneline
dd4472c (HEAD -> master) we should not forget to enjoy
2bb9bb4 add half an onion
2d79e7e adding ingredients and instructions
```
````
---
## Recovering from committing to the wrong branch
It is easy to forget to create a branch or to create it and forget to switch to
it when committing changes.
Here we assume that we made a couple of commits but we realize they went to the
wrong branch.
**Solution 1 using git cherry-pick**:
1. Make sure that the correct branch exists and if not, create it. Make sure to
create it from the commit hash where you wish you had created it from: `git
branch `
2. Switch to the correct branch.
3. `git cherry-pick ` can be used to take a specific commit to the
current branch. Cherry-pick all commits that should have gone to the correct
branch, **from oldest to most recent**.
4. Rewind the branch that accidentally got wrong commits with `git reset --hard` (see also above).
**Solution 2 using git reset --hard** (makes sense if the correct branch should
contain all commits of the accidentally modified branch):
1. Create the correct branch, pointing at the latest commit: `git branch `.
2. Check with `git log` or `git graph` that both branches point to the same, latest, commit.
3. Rewind the branch that accidentally got wrong commits with `git reset --hard` (see also above).
## Recovering from merging/pulling into the wrong branch
`git merge`, `git rebase`, and `git pull` modify the **current** branch, never
the other branch. But sometimes we run this command on the wrong branch.
1. Check with `git log` the commit hash that you would like to rewind the
wrongly modified branch to.
2. Rewind the branch that accidentally got wrong commits with `git reset --hard ` (see also above).
## Recovering from conflict after git pull
`git pull` can create a conflict since `git pull` always also includes a `git merge` (more about this
in the [collaborative Git lesson](https://coderefinery.github.io/git-collaborative/)).
The recovery is same as described in {ref}`conflict-resolution`. Either
resolve conflicts or abort the merge with `git merge --abort`.
---
````{challenge} Undoing-4: Test your understanding
1. What happens if you accidentally remove a tracked file with `git rm`, is it gone forever?
2. Is it OK to modify commits that nobody has seen yet?
3. What situations would justify to modify the Git history and possibly remove commits?
```{solution}
1. It is not gone forever since `git rm` creates a new commit. You can simply revert it!
2. If you haven't shared your commits with anyone it can be alright to modify them.
3. If you have shared your commits with others (e.g. pushed them to GitHub), only extraordinary
conditions would justify modifying history. For example to remove sensitive or secret information.
```
````