Skip to content

bobby_dreamer

Git Theory - 3 - revert, reset and restore

notes, git5 min read

Undos are the most important thing when you are using computers, its something, one cannot do in any other sector cheaply. Ctrl+Z and Ctrl+Y are something we do very often when working, its easier way to try something new and go back if something fails or doesnt work. Git has many ways to undo work and it all depends on different scenarios, below are some simple ways, i have tried and did it.

Git commands to undo changes,

  • git checkout
  • git reset
  • git revert
  • git restore
    • As this is still in "THIS COMMAND IS EXPERIMENTAL. THE BEHAVIOR MAY CHANGE", i have not explored this.

# Undo using git checkout

Git checkout has multiple functionalities,

  1. Updating the working directory with content from index/staging.
  2. It can move HEAD to another branch or a commit. If you checkout to a commit, you end up with detached HEAD.

Here for undo, we will try the first option. This command doesn't make any changes to the history.

Test Setup

1rm -rf .git *.*
2
3git init
4echo "Committed" > fileA.txt
5git commit -a -m "Initial Commit"
6
7echo "Stage this" >> fileA.txt
8git add fileA.txt
9
10echo "Changes in working directory" >> fileA.txt

git checkout setup

git checkout -- fileA.txt

  • Checkout has multiple functions by putting two hypens -- you are saying you are dealing with a file
  • Copy fileB.txt from staging area to working directory
  • git checkout -- .
    • dot stands for the current directory
    • overwrites all the files in the working directory.

In the below picture, you can see the after checkout file in working directory has been replaced with file from index/staging area.

git checkout -- fileA.txt

git checkout HEAD -- fileA.txt

  • Copies fileA.txt from that commit point in repo to staging area
  • Copies fileA.txt from staging area to working directory

In the below image,

  • Blue : Shows the content of fileA.txt before the command was executed.
  • Green : Show the content of fileA.txt after the command was execute.

git checkout HEAD -- fileA.txt

Below options can be used to reset specific subfolders,

  • git checkout -- path/
  • git checkout HEAD -- path/
  • Go into the folder you want to reset and then do git checkout -- .

# Undo using git reset

Git reset is specifically about updating your branch, moving the tip in order to add or remove commits from the branch. This operation changes the commit history.

There are three variants with git reset

  • Soft : Doesn't touch the index/staging or work directory, but all the changes show up as changes to be committed with git status. If you re-commit, the resulting commit will have exactly the same content as where you were before you reset. So can be used to squash commits.

  • Mixed : Default. It resets the index/staging area but does not touch the working directory. Here any differences between the latest commit and the one you reset to will show up as local modifications in git status.

  • Hard : Index/Stage and working directory are updated with data what was in commit point it was reset to.

git reset

Test Setup

1rm -rf .git *.*
2
3git init
4echo "Line 1" > fileA.txt
5git add .
6git commit -m "Initial Commit"
7
8echo "Line 2" >> fileA.txt
9echo "Line 1" > fileB.txt
10git add .
11git commit -m "Second Commit"
12
13echo "Line 3" >> fileA.txt
14echo "Line 2" >> fileB.txt
15echo "Line 1" > fileC.txt
16git add .
17git commit -a -m "Third Commit"
18
19echo "Line 4" >> fileA.txt
20echo "Line 3" >> fileB.txt
21echo "Line 2" >> fileC.txt
22git commit -a -m "Fourth Commit"
23
24echo "Line 5 - Staged" > fileA.txt
25git add fileA.txt
26echo "Line 4 - Working directory" >> fileB.txt
27echo "Line 5 - Working directory" >> fileC.txt

This is how it looks after running the setup

1Initial Commit | fileA.txt
2 | ---------
3 | Line 1
4
5Second Commit | fileA.txt | fileB.txt
6 | --------- | ---------
7 | Line 1 | Line 1
8 | Line 2 |
9
10Third Commit | fileA.txt | fileB.txt | fileC.txt
11 | --------- | --------- | ---------
12 | Line 1 | Line 1 | Line 1
13 | Line 2 | Line 2 |
14 | Line 3 | |
15
16Fourth Commit | fileA.txt | fileB.txt | fileC.txt
17 | --------- | --------- | ---------
18 | Line 1 | Line 1 | Line 1
19 | Line 2 | Line 2 | Line 2
20 | Line 3 | Line 3 |
21 | Line 4 | |
22
23# Current state
24--------------------------------------------------------------------------
25Working directory | Staged
26---------------------------------------------------------| -------
27fileB.txt | fileC.txt | fileA.txt
28--------- | ---------- | ---------
29Line 1 | Line 1 | Line 5 - Staged
30Line 2 | Line 2 |
31Line 3 | Line 5 - Working directory |
32Line 4 - Working directory | |

Same as show above

git reset --soft

git reset --soft

This is how it looks after git reset --soft HEAD~2

  • HEAD~2 means we are going back 2 commits and resetting to "Second Commit".
  • By comparing the before and after reset, we can say that nothing has happened to the content in staging and working directory other than we have few "Changes to be committed".

git reset --soft

git reset --mixed

We run our setup scripts again and this is how it looked at second commit

1Second Commit | fileA.txt | fileB.txt
2 | --------- | ---------
3 | Line 1 | Line 1
4 | Line 2 |

Now we run git reset --mixed HEAD~2,

  • HEAD~2 means we are going back 2 commits and resetting to "Second Commit". So we are going to be losing changes that was made in "Third Commit" and "Fourth Commit".
  • Index/Stage area data was reset with data what was in "Second Commit".
  • Working directory is not affected.
  • fileC.txt is not found in index/staging because it was added in "Third Commit" only.

git reset --mixed

To proceed with the changes, you need to git add . and git commit.

git reset --hard

We run our setup scripts again and then run git reset --hard HEAD~2.

  1. fileC.txt is missing.
  2. Index/Stage and Working directory is reset with data that was in "Second Commit".
  3. "Third Commit" and "Fourth Commit" are lost but not entirely(By default Git gives you 30 days before it cleans up those orphan commits)

git reset --hard

Recovering from git reset --hard

  • Whenever tip of the branches are updated(created or cloned, checked-out, renamed, or any commits), its recorded and that mechanism is call Reflog.
  • Here we use information from Reflog and again reset --hard and go back to "Fourth Commit"

git reset --hard

From the above output, we know, we lost data from

  • Index/Stage
  • Working directory

Its recommended before doing any git reset --hard, do a git stash -u.

You also want to do bit of cleanup after after git reset --hard like below

1git reset --hard
2
3# '-f' is force, '-d' is remove directories.
4git clean -fd

Main thing to remember is "Removing specific commit is not possible to with git reset" thats what git revert is for.

# Git Revert

git revert command is about making a new commit that reverts the changes made by other commits. This command adds new history to the project (it doesn't modify existing history).

Below i am going show some git revert by examples, simple ones.

Test 1 : Plan
  1. Creating a file with "one", "two", "three" and "four" in each line + doing a commit
  2. Updating two to 2 and adding a newfile.txt + doing a commit
  3. Updating four to 4
  4. Revert (2). What happens is
    • New commit for revert
    • newfile.txt goes missing
    • Updates onetwothreefour.txt, replaces 2 with two

Setup

1rm -rf .git *.*
2
3git init
4echo "one" > onetwothreefour.txt
5echo "two" >> onetwothreefour.txt
6echo "three" >> onetwothreefour.txt
7echo "four" >> onetwothreefour.txt
8git add .
9git commit -m "Adding file onetwothreefour.txt"
10
11sed -i 's/two/2/' onetwothreefour.txt
12touch newfile.txt
13git add .
14git commit -m "Update two to 2 and added new file"
15
16sed -i 's/four/4/' onetwothreefour.txt
17git commit -a -m "Update four to 4"

git revert HEAD~1

With git revert hash --no-edit it doesn't start editor, basically you are accepting the default commit message for revert.

Test 2 : Plan
  1. Creating and appending data to file
  2. Updating all content in one file and adding a new file.
  3. Make few updates and commit
  4. Revert (2). What happens ?
    • 123.txt reverted successfully without conflicts
    • abc.txt has conflicts which has to be handled manually.

Setup

1rm -rf .git *.*
2
3git init
4echo "abc" > abc.txt
5git add .
6git commit -m "Added abc in abc.txt"
7
8echo "def" >> abc.txt
9git commit -a -m "Added def in abc.txt"
10
11echo "ghi" >> abc.txt
12dd if=abc.txt of=output.txt conv=ucase
13rm abc.txt
14mv output.txt abc.txt
15echo "123" >> 123.txt
16git add .
17git commit -m "Added ghi, CAPS in abc.txt and 123 to 123.txt"
18
19echo "JKL" >> abc.txt
20echo "456" >> 123.txt
21sed -i 's/123/onetwothree/' 123.txt
22git commit -a -m "Added JKL in abc.txt and 456 to 123.txt"
23
24echo "MNO" >> abc.txt
25echo "789" >> 123.txt
26git commit -a -m "Added MNO in abc.txt and 789 to 123.txt"
27
28Sushanth@Sushanth-VAIO MINGW64 /d/GITs/test4
29$ git lol
30* e561d30 - (HEAD -> master) Added MNO in abc.txt and 789 to 123.txt (9 seconds ago) <Sushanth Bobby Lloyds>
31* 81cf3dd - Added JKL in abc.txt and 456 to 123.txt (9 seconds ago) <Sushanth Bobby Lloyds>
32* efada58 - Added ghi, CAPS in abc.txt and 123 to 123.txt (10 seconds ago) <Sushanth Bobby Lloyds>
33* 7ace0d0 - Added def in abc.txt (11 seconds ago) <Sushanth Bobby Lloyds>
34* 54f77e1 - Added abc in abc.txt (11 seconds ago) <Sushanth Bobby Lloyds>

Current state of both the files

1$ cat abc.txt | $ cat 123.txt
2ABC | onetwothree
3DEF | 456
4GHI | 789
5JKL |
6MNO |

After git revert HEAD~2. Below are the current state of the files.

1$ cat abc.txt | $ cat 123.txt
2ABC | 456
3DEF | 789
4GHI |
5JKL |
6MNO |

123.txt file

  • There is no conflict.
  • Changes that was made in HEAD~2 were reverted, insense in the reverted commit this file was created and added data "123" in the next commit "456" was appended and "123" was updated to "onetwothree".

abc.txt

  • We have a conflict
  • Between <<<<<<< HEAD and ======= its what in the HEAD
  • Below ======= it what before HEAD~2
  • We have to manually update the abc.txt file.

After updating the file, it looks like this,

1$ cat abc.txt
2abc
3def
4GHI
5JKL
6MNO
7
8# follow the git instructions
9$ git status
10On branch master
11You are currently reverting commit efada58.
12 (fix conflicts and run "git revert --continue")
13 (use "git revert --abort" to cancel the revert operation)
14
15Unmerged paths:
16 (use "git reset HEAD <file>..." to unstage)
17 (use "git add/rm <file>..." as appropriate to mark resolution)
18
19 deleted by them: 123.txt
20 both modified: abc.txt
21
22no changes added to commit (use "git add" and/or "git commit -a")

Since we have updated file abc.txt and removed all the git markers. We will have to add and revert continue

1Sushanth@Sushanth-VAIO MINGW64 /d/GITs/test4 (master|REVERTING)
2$ git add .
3warning: LF will be replaced by CRLF in 123.txt.
4The file will have its original line endings in your working directory.
5
6# Once you enter the below command, it opens the default editor, i am going with default message, so :wq
7Sushanth@Sushanth-VAIO MINGW64 /d/GITs/test4 (master|REVERTING)
8$ git revert --continue
9[master ed6bbb7] Revert "Added ghi, CAPS in abc.txt and 123 to 123.txt"
10 1 file changed, 3 insertions(+), 3 deletions(-)
11
12Sushanth@Sushanth-VAIO MINGW64 /d/GITs/test4 (master)
Test 3 : Plan
  1. Concatenating string and committing changes.
  2. Reverting HEAD~2. What happens ? Conflicts. That has to be changed manually.

Setup

1rm -rf .git *.*
2
3git init
4echo -n "abc" > abc.txt
5git add .
6git commit -m "Added abc in abc.txt"
7
8echo -n "def" >> abc.txt
9git commit -a -m "Added def in abc.txt"
10
11echo -n "GHI" >> abc.txt
12git commit -a -m "Added ghi in abc.txt"
13
14echo -n "JKL" >> abc.txt
15git commit -a -m "Added JKL in abc.txt"
16
17echo -n "MNO" >> abc.txt
18git commit -a -m "Added MNO in abc.txt"
19
20Sushanth@Sushanth-VAIO MINGW64 /d/GITs/test4 (master)
21$ cat abc.txt
22abcdefGHIJKLMNO
23
24Sushanth@Sushanth-VAIO MINGW64 /d/GITs/test4 (master)
25$ git lol
26* 193f710 - (HEAD -> master) Added MNO in abc.txt (11 seconds ago) <Sushanth Bobby Lloyds>
27* ba22751 - Added JKL in abc.txt (12 seconds ago) <Sushanth Bobby Lloyds>
28* 3207be7 - Added ghi in abc.txt (12 seconds ago) <Sushanth Bobby Lloyds>
29* 713ab79 - Added def in abc.txt (13 seconds ago) <Sushanth Bobby Lloyds>
30* 1d3ed23 - Added abc in abc.txt (13 seconds ago) <Sushanth Bobby Lloyds>

Here we will try to revert commit "3207be7" which added string "ghi".

1$ git revert HEAD~2
2error: could not revert 3207be7... Added ghi in abc.txt
3hint: after resolving the conflicts, mark the corrected paths
4hint: with 'git add <paths>' or 'git rm <paths>'
5hint: and commit the result with 'git commit'
6
7Sushanth@Sushanth-VAIO MINGW64 /d/GITs/test4 (master|REVERTING)

In the below image, three commands were executed.

  1. cat abc.txt : Shows with git conflict markers, first part of the marker shows whats in HEAD and second part shows what was before the reverted commit.
  2. git show HEAD~2 -- abc.txt : Shows change that was made in that commit. From the image, we can clearly say that +abcdefGHI was added.
  3. git diff HEAD~2 HEAD : Show difference between HEAD~2 to HEAD.

git revert

Resolving conflicts after updating the file abc.txt

1$ cat abc.txt
2abcdefJKLMNO
3
4Sushanth@Sushanth-VAIO MINGW64 /d/GITs/test4 (master|REVERTING)
5$ git status
6On branch master
7You are currently reverting commit 3207be7.
8 (fix conflicts and run "git revert --continue")
9 (use "git revert --abort" to cancel the revert operation)
10
11Unmerged paths:
12 (use "git reset HEAD <file>..." to unstage)
13 (use "git add <file>..." to mark resolution)
14
15 both modified: abc.txt
16
17no changes added to commit (use "git add" and/or "git commit -a")
18
19Sushanth@Sushanth-VAIO MINGW64 /d/GITs/test4 (master|REVERTING)
20$ git add .
21
22Sushanth@Sushanth-VAIO MINGW64 /d/GITs/test4 (master|REVERTING)
23$ git revert --continue
24[master 8a42d6f] Revert "Added ghi in abc.txt"
25 1 file changed, 1 insertion(+), 1 deletion(-)
26
27Sushanth@Sushanth-VAIO MINGW64 /d/GITs/test4 (master)

# Git Restore

git-restore is about restoring files in the working tree from either the index or another commit.

  • git restore abc.txt by default will restore file abc.txt to match the version in the index.
  • git restore --staged abc.txt can be used to restore a file in the index to match the version in HEAD.
  • git restore --source=HEAD --staged --worktree abc.txt
    • restore both the index and the working tree
    • Equivalent to git reset --hard

Thats all the undo's i have for now.


# Things to remember

  • Don't add large files to git, even if you git rm or git reset they will be in there dangling. Its not easy to find them.
  • Don't use master/main branch for development purposes, they should have only production ready, releasable code.
  • Destructive commands in git
    1git checkout HEAD -- fn.ext
    2git checkout -- .
    3git reset --hard
    4git clean -f

# Next steps

  • Branching : Git Branching
  • Internals : Git Internals
  • Collaboration : Git remote repository
  • Git Everyday : Git flowchart, shortcuts and references
  • Origin : How it all began. What is git ? and Terminologies used in this series.
  • Basics : config, init, add, rm, .gitignore, commit, log, blame, diff, tag, describe, show and stash