赞
踩
这一部分主要讲的是 取消(undo) 变化 和在不同的时间锚点跳来跳去,以 command 为主。
设计到的commits有:
checkout
的一部分作用,即切换分枝在 git 分支操作 中有提到过,不过 checkout
本身的用途更多。有些开发甚至觉得 checkout
的功能太多了,最终将功能分割之后生成了新的 switch
和 restore
。
除了分支管理之外,checkout
还能用来还原文件和 undo history。
下面会 checkout 一个 commit hash:
# 可以完整cv整个hash,或者截取前 7 位作为hash key ➜ basic git:(main) ✗ git checkout f8b2e15 Note: switching to 'f8b2e15'. 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 f8b2e15 start work on outline and characters ➜ basic git:(f8b2e15) ✗ git status HEAD detached at f8b2e15 Untracked files: (use "git add <file>..." to include in what will be committed) git.md nothing added to commit but untracked files present (use "git add" to track)
现在解释一下 detached HEAD
是什么意思,正常情况下,当前 branch 指向当前 commit,而当前 head 指向当前 branch,也就可以理解成是当前 commit 的 reference,这个时候的 head 是当前 commit 的 symlink:
软件上看起来是这样的:
但是当直接 checkout 到一个 commit 之后,当前的分支依旧之乡最后一个 commit,HEAD 则是只想当前的 commit:
也就是说 HEAD 从 branch 上分离(detach)了,软件上看起来是这样的:
这时候的 HEAD 直接作为当前 commit 的 reference。
checkout commit 的用途有
查看原始文件
大多是时候发生在某个版本不工作了,然后要要 revert 到某一个版本
创建一个新的分支
就像 switch 到一个新的分支会创建一个新的历史一样,checkout 到某一个 commit 也会创建一个新的历史分支
➜ basic git:(main) ✗ git checkout f8b2e15
➜ basic git:(f8b2e15) ✗ git switch -c new-ch2
Switched to a new branch 'new-ch2'
➜ basic git:(new-ch2) ✗ git commit -m "create new version of ch2"
[new-ch2 12da43a] create new version of ch2
1 file changed, 1 insertion(+)
create mode 100644 ch2.txt
➜ basic git:(new-ch2) ✗
此时的分支看起来如下:
此时的 HEAD 不是一个 detached 状态,而是重新绑定到了新创建的 new-ch2
分支上。而该分支没有保存的变化,依旧保存在另一个分支上。
如果没有做任何的变动,想要切换到原有的分支,可以直接使用 git checkout branchname
执行。
具体语法为:git checkout HEAD~1
,其中 HEAD~
后跟数字,如果是 1 就惠滚到上一个 commit,以此类推。
图解如下:
顺便上面的 mermaid 语法为:
gitGraph
commit id: "HEAD~5"
commit id: "HEAD~4"
commit id: "HEAD~3"
commit id: "HEAD~2"
commit id: "HEAD~1"
commit type: HIGHLIGHT
运行如下:
➜ basic git:(new-ch2) ✗ git checkout main Switched to branch 'main' ➜ basic git:(main) ✗ git checkout HEAD~2 Note: switching to 'HEAD~2'. 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 b18e966 update chapter 1
这个时候返回有两个方法:
记住原本分支
使用 git switch -
,该指令会切换到上一个所在的分支,如:
➜ basic git:(b18e966) ✗ git switch -
Previous HEAD position was b18e966 update chapter 1
Switched to branch 'main'
该指令可以用于重制已经修改的文件,这里会用一个新的 repo 做例子,初步设置如下:
➜ undo git:(main) ✗ touch cat.txt dog.txt ➜ undo git:(main) ✗ echo "first commit" > cat.txt ➜ undo git:(main) ✗ echo "first commit" > dog.txt ➜ undo git:(main) ✗ git status On branch main No commits yet Untracked files: (use "git add <file>..." to include in what will be committed) cat.txt dog.txt nothing added to commit but untracked files present (use "git add" to track) ➜ undo git:(main) ✗ git add . ➜ undo git:(main) ✗ git commit -m "first commits" [main (root-commit) 6c98eb0] first commits 2 files changed, 2 insertions(+) create mode 100644 cat.txt create mode 100644 dog.txt ➜ undo git:(main) cat cat.txt first commit ➜ undo git:(main) ✗ echo 'second commit' >> cat.txt ➜ undo git:(main) ✗ echo 'second commit' >> dog.txt ➜ undo git:(main) ✗ cat cat.txt first commit second commit ➜ undo git:(main) ✗ git add . ➜ undo git:(main) ✗ git commit -m "second commit" [main 5a999a7] second commit 2 files changed, 2 insertions(+) # 重复若干次
准备完了之后就可以用 git checkout HEAD <file>
,缩写为 git checkout -- <filename>
去重制操作了,如:
➜ undo git:(main) git status On branch main 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: cat.txt modified: git.md no changes added to commit (use "git add" and/or "git commit -a") ➜ undo git:(main) ✗ git checkout HEAD cat.txt Updated 1 path from 4899106 ➜ undo git:(main) ✗ git status On branch main 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: git.md no changes added to commit (use "git add" and/or "git commit -a") ➜ undo git:(main) ✗ git status On branch main 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: cat.txt modified: dog.txt modified: git.md no changes added to commit (use "git add" and/or "git commit -a") ➜ undo git:(main) ✗ git checkout -- *.txt ➜ undo git:(main) ✗ git status On branch main 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: git.md no changes added to commit (use "git add" and/or "git commit -a")
这个的使用场景可能在:
你真的搞砸了很多
重制 db 文件
比如说我们的项目就是从 csv 中读取 db 文件的,然后每天都必须要更新 csv 中的 ref date,这个时候更新分支就会因为 csv 不 match 导致 conflict。
像上文提到的,restore 是一个比较新的命令,用于重置一些 checkout 的功能。
该指令可以用于重制已经修改的文件,对标 git checkout HEAD <file>
。
➜ undo git:(main) ✗ git status On branch main 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: dog.txt modified: git.md no changes added to commit (use "git add" and/or "git commit -a") ➜ undo git:(main) ✗ git restore dog.txt ➜ undo git:(main) ✗ git status On branch main 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: git.md no changes added to commit (use "git add" and/or "git commit -a") ➜ undo git:(main) ✗
另一个 flag 可以用来将当前文件还原到几个 commit 之前,语法为:git restore --source <HEAD~1|hashedvalue> <filename>
,还愿的方法使用上一条指令即可。
例:
➜ undo git:(main) ✗ cat dog.txt
first commit
second commit
third commit
➜ undo git:(main) ✗ git restore --source HEAD~2 dog.txt
➜ undo git:(main) ✗ cat dog.txt
first commit
➜ undo git:(main) ✗ git restore dog.txt
➜ undo git:(main) ✗ cat dog.txt
first commit
second commit
third commit
具体指令:git restore --staged <file>
,这条指令可以将已经 staged 文件拉出来,如:
➜ undo git:(main) ✗ touch wrongfile.txt ➜ undo git:(main) ✗ git status On branch main 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: git.md Untracked files: (use "git add <file>..." to include in what will be committed) wrongfile.txt no changes added to commit (use "git add" and/or "git commit -a") ➜ undo git:(main) ✗ git add . ➜ undo git:(main) ✗ git status On branch main Changes to be committed: (use "git restore --staged <file>..." to unstage) modified: git.md new file: wrongfile.txt ➜ undo git:(main) ✗ git restore --staged wrongfile.txt ➜ undo git:(main) ✗ git status On branch main Changes to be committed: (use "git restore --staged <file>..." to unstage) modified: git.md Untracked files: (use "git add <file>..." to include in what will be committed) wrongfile.txt
顺便,如果直接用 git status
,git 会提示可以用什么指令进行操作,如 git restore --staged <file>...
, git add <file>...
, git restore <file>..
。
git reset
用于重置 commits,我主要用来将几个 commits squash 到一个 commits 里面去,使用 git rest
会保留当前的变化。
案例:
➜ undo git:(main) git log --oneline da7603f (HEAD -> main) mistake commit2 a34f0c8 mistake commit 50840de third commit 5a999a7 second commit 6c98eb0 first commits (END) ➜ undo git:(main) git reset 50840de Unstaged changes after reset: M cat.txt M dog.txt M git.md ➜ undo git:(main) ✗ git status On branch main 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: cat.txt modified: dog.txt modified: git.md Untracked files: (use "git add <file>..." to include in what will be committed) wrongfile.txt no changes added to commit (use "git add" and/or "git commit -a") ➜ undo git:(main) ✗ git log --oneline 50840de (HEAD -> main) third commit 5a999a7 second commit 6c98eb0 first commits (END)
可以看到,本来的变化没有丢还在这里。
对于 git 来说,它会重新将 HEAD 指向提供的 commit hash 值,并且将中间的变化保存在 working directory 中。
我常用的做法就是清理分支上的 commits(尽量保证一个修 bug 的 MR/PR 一个 commits,功能性的保证有理由的 staging,一般来说小功能在 3 个以下,大功能 5 个一下这样),或者是将当前的变化带到其他的分支上。当然,后者用 git stash
也可以。
如果想要将当前的变化和修改的文件全都删除,可以加上 --hard
这个 flag,其语法为 git reset --hard <commit>
,如:
➜ undo git:(main) git add . ➜ undo git:(main) ✗ git commit -m "mistake commit" [main fa07782] mistake commit 2 files changed, 2 insertions(+) ➜ undo git:(main) git log --oneline fa07782 (HEAD -> main) mistake commit 0608200 fourth commit 50840de third commit 5a999a7 second commit 6c98eb0 ➜ undo git:(main) ✗ git reset --hard HEAD~1 HEAD is now at 0608200 fourth commit ➜ undo git:(main) git status On branch main nothing to commit, working tree clean ➜ undo git:(main) git log --oneline 0608200 (HEAD -> main) fourth commit 50840de third commit 5a999a7 second commit 6c98eb0 first commits (END)
revert 和 reset 有点像,但是 revert 会保留之前的 commit,将文件放到 working directory 中,并且创建一个新的 commit 说明之前的 commit 被 revert 了。
举例说明:
➜ undo git:(main) git log --oneline fbd7250 (HEAD -> main) mistake commit2 2f0f2ce mistake commit 0608200 fourth commit 50840de third commit 5a999a7 second commit 6c98eb0 first commits (END) ➜ undo git:(main) ✗ git add . ➜ undo git:(main) ✗ git commit -m "mistake commit" [main 2f0f2ce] mistake commit 3 files changed, 36 insertions(+), 1 deletion(-) ➜ undo git:(main) git add . ➜ undo git:(main) ✗ git commit -m "mistake commit2" [main fbd7250] mistake commit2 1 file changed, 1 insertion(+) ➜ undo git:(main) git log --oneline ➜ undo git:(main) git revert 0608200 error: Your local changes to the following files would be overwritten by merge: git.md Please commit your changes or stash them before you merge. Aborting fatal: revert failed ➜ undo git:(main) ✗ git revert 0608200 Auto-merging cat.txt CONFLICT (content): Merge conflict in cat.txt Auto-merging dog.txt CONFLICT (content): Merge conflict in dog.txt Auto-merging git.md CONFLICT (content): Merge conflict in git.md error: could not revert 0608200... fourth commit hint: After resolving the conflicts, mark them with hint: "git add/rm <pathspec>", then run hint: "git revert --continue". hint: You can instead skip this commit with "git revert --skip". hint: To abort and get back to the state before "git revert", hint: run "git revert --abort". ➜ undo git:(main) ✗ git add . ➜ undo git:(main) ✗ git revert --continue [main 6c50464] Revert "fourth commit" 2 files changed, 1 insertion(+), 1 deletion(-) delete mode 100644 wrongfile.txt ➜ undo git:(main) git status On branch main nothing to commit, working tree clean ➜ undo git:(main) git log --oneline 6c50464 (HEAD -> main) Revert "fourth commit" fbd7250 mistake commit2 2f0f2ce mistake commit 0608200 fourth commit 50840de third commit 5a999a7 second commit 6c98eb0 first commits (END)
可以看到,working tree clean
标记修改的文件已经没有了,但是修改的 commits 还存在。
从个人经验上来说,我用 revert 就是为了修正某些已经实现的功能(by fixing one bug we created more bugs),但是别的同事机器上已经有了那个 commits,不可能说重改整个历史,这样别人做的 commits 也没有了。
即,如果所有的变化都是本地的,那么使用 reset 会方便些。如果变化推到了 remote,但是 没有其他的同事 使用这个 branch,使用 reset 也可以。否则就应该使用 reset。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。