In the previous article, I explain the concept of three trees and some commands to handle files in working trees and index. In this article, I use git reset to control branch.
Reset to specified commit
I already explained how I can use git checkout
to move the HEAD to different commit. But it didn't update branch file in .git. This means the branch still points to the latest commit, while I am in the different commit.
git reset
updates branch instead of HEAD so that I actually undo the commit entirely.
Add another commit for test
I was in a middle of modifying hello.txt in the previous article.
gitDeepDive> git log --oneline --graph
* 367c2d0 (HEAD -> dev) Update news
* 2adbcac (test, master) Add doc folder and files
* 16f1fa8 commit hello.txt
gitDeepDive> git status
On branch dev
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
modified: hello.txt
Let's commit the change now.
gitDeepDive> git commit -m "update hello.txt"
[dev 867d90c] update hello.txt
1 file changed, 2 insertions(+)
gitDeepDive> git log --oneline --graph
* 867d90c (HEAD -> dev) update hello.txt
* 367c2d0 Update news
* 2adbcac (test, master) Add doc folder and files
* 16f1fa8 commit hello.txt
Then, I modify the file again.
echo "The fifth line" >> hello.txt
git add hello.txt
echo "The sixth line" >> hello.txt
Now I have different version of hello.txt in HEAD, index and working tree.
gitDeepDive> git status
On branch dev
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
modified: hello.txt
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: hello.txt
gitDeepDive> git ls-files --stage
100644 44f41854d770f2a38d368936b14975d280cbd950 0 docs/article.txt
100644 2b366bf2f2784dbf26fcd56e1cedb3afc1345753 0 docs/news.txt
100644 95edf347f35cce2273a54c0084f54923d6e7ad1d 0 hello.txt
Soft reset
git reset
has three pattern. Soft, Mix and Hard. Let's start from soft. HEAD^ means previous commit. This is easy shortcut as I don't have to check commit id. If I want to go back two commits, then simply add one more ^
like HEAD^^.
git reset --soft HEAD^
Let's see how git handles the command. The HEAD points dev branch, and dev branch points to 367c2d0 which is the previous commit, but git didn't modify index and working tree.
gitDeepDive> git log --oneline
367c2d0 (HEAD -> dev) Update news
2adbcac (test, master) Add doc folder and files
16f1fa8 commit hello.txt
gitDeepDive> git status
On branch dev
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
modified: hello.txt
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: hello.txt
gitDeepDive> git ls-files --stage
100644 44f41854d770f2a38d368936b14975d280cbd950 0 docs/article.txt
100644 2b366bf2f2784dbf26fcd56e1cedb3afc1345753 0 docs/news.txt
100644 95edf347f35cce2273a54c0084f54923d6e7ad1d 0 hello.txt
The gray color boxes are the ones changing from the previous image.
Mixed reset
Let's try mixed option now. As I already in commit 367c2d0, I simply reset to current HEAD but use --mixed
option.
gitDeepDive> git reset --mixed HEAD
Unstaged changes after reset:
M hello.txt
See the current status. I can see that index has been reset but not working tree.
gitDeepDive> git log --oneline
367c2d0 (HEAD -> dev) Update news
2adbcac (test, master) Add doc folder and files
16f1fa8 commit hello.txt
gitDeepDive> git ls-files --stage
100644 44f41854d770f2a38d368936b14975d280cbd950 0 docs/article.txt
100644 2b366bf2f2784dbf26fcd56e1cedb3afc1345753 0 docs/news.txt
100644 20fe8be9820a49252e2a4dd37a60e678cd5cda14 0 hello.txt
gitDeepDive> git status
On branch dev
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: hello.txt
no changes added to commit (use "git add" and/or "git commit -a")
gitDeepDive>
In the diagram, only the difference is that pointer to hello.txt in index is "reset".
Hard reset
Finally, try hard reset.
gitDeepDive> git reset --hard HEAD
HEAD is now at 367c2d0 Update news
git now reset working tree files as well.
gitDeepDive> git log --oneline
367c2d0 (HEAD -> dev) Update news
2adbcac (test, master) Add doc folder and files
16f1fa8 commit hello.txt
gitDeepDive> git ls-files --stage
100644 44f41854d770f2a38d368936b14975d280cbd950 0 docs/article.txt
100644 2b366bf2f2784dbf26fcd56e1cedb3afc1345753 0 docs/news.txt
100644 20fe8be9820a49252e2a4dd37a60e678cd5cda14 0 hello.txt
gitDeepDive> git status
On branch dev
nothing to commit, working tree clean
As we all know already, we can restore to any point of time as long as git snapshot contents. I can go back to commit 867d90c easily. However, I lose the change in working tree, as git doesn't snapshot the contents.
gitDeepDive> git reset --hard 867d90c
HEAD is now at 867d90c update hello.txt
gitDeepDive> git log --oneline
867d90c (HEAD -> dev) update hello.txt
367c2d0 Update news
2adbcac (test, master) Add doc folder and files
16f1fa8 commit hello.txt
gitDeepDive> git ls-files --stage
100644 44f41854d770f2a38d368936b14975d280cbd950 0 docs/article.txt
100644 2b366bf2f2784dbf26fcd56e1cedb3afc1345753 0 docs/news.txt
100644 7a218e826670e77d05c1c244b514a7f449056752 0 hello.txt
gitDeepDive> git status
On branch dev
nothing to commit, working tree clean
Delete branch
What happens if I delete branch? Let's try. I need to move out of the dev branch to delete it.
gitDeepDive> git switch master
Switched to branch 'master'
gitDeepDive> git branch -D dev
Deleted branch dev (was 867d90c).
gitDeepDive> git branch -a
* master
test
When I looked into .git folder, I don't see any branch info in refs/heads as I did git gc
. Instead I see them in packed-refs both point to the same commit.
gitDeepDive> cat .git\packed-refs
# pack-refs with: peeled fully-peeled sorted
2adbcacc0047a991956dedb4b16691ba244674b3 refs/heads/master
2adbcacc0047a991956dedb4b16691ba244674b3 refs/heads/test
Now dev branch is gone, but the commit still exists.
gitDeepDive> git cat-file commit 867d90c
tree edbfe822158fb3be005aca22897a0395fa2c9252
parent 367c2d000be0ffbb640252384c820ce472fe32a4
author Kenichiro Nakamura <kenakamu@microsoft.com> 1588842488 +0900
committer Kenichiro Nakamura <kenakamu@microsoft.com> 1588842488 +0900
update hello.txt
I can checkout to the commit. I see "detached HEAD" again but I know what it means so not scary at all.
gitDeepDive> git checkout 867d90c
Note: switching to '867d90c'.
You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by switching back to a branch.
If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -c with the switch command. Example:
git switch -c <new-branch-name>
Or undo this operation with:
git switch -
Turn off this advice by setting config variable advice.detachedHead to false
HEAD is now at 867d90c update hello.txt
I can create new dev branch from here.
gitDeepDive> git branch -a
* (HEAD detached at 867d90c)
master
test
gitDeepDive> git switch -c dev
Switched to a new branch 'dev'
gitDeepDive> git branch -a
* dev
master
test
gitDeepDive> git log --oneline
867d90c (HEAD -> dev) update hello.txt
367c2d0 Update news
2adbcac (test, master) Add doc folder and files
16f1fa8 commit hello.txt
Summary
As long as git takes snapshot by either git add
or git commit
, there is a way to recover them, but I will lose my change if I delete working tree. I will explain how merge work in the next article.
Top comments (0)