Missing Semester 计算机教育中缺失的一课 Lecture 05 Version Control and Git

前言

acmer 听这种底层概念真的毫无压力!

基本命令

创建

❯ git init
在 /home/Bluuue/Desktop/git-demo/.git/ 初始化空的 Git 仓库
❯ ls -la  
总计 4  
drwxr-xr-x 1 Bluuue Bluuue   24  9月11日 00:14 .  
drwxr-xr-x 1 Bluuue Bluuue 1458  9月10日 18:49 ..  
drwxr-xr-x 1 Bluuue Bluuue  128  9月11日 00:14 .git  
-rw-r--r-- 1 Bluuue Bluuue   12  9月11日 00:11 test.txt

首先,git help commit 可以查询某个命令的用法。之后,对于当前工作目录,首先用 git init 创建一个空的 git 仓库。此时可以发现目录中多了一个名为 .git 的隐藏文件夹,这个就是用来存放仓库数据的。

❯ git switch test
切换到分支 'test'
❯ echo "v3" >> test.txt
❯ git status
位于分支 test
尚未暂存以备提交的变更:
  (使用 "git add <文件>..." 更新要提交的内容)
  (使用 "git restore <文件>..." 丢弃工作区的改动)
        修改:     test.txt

修改尚未加入提交(使用 "git add" 和/或 "git commit -a")
❯ git add test.txt
❯ git status
位于分支 test
要提交的变更:
  (使用 "git restore --staged <文件>..." 以取消暂存)
        修改:     test.txt

❯ echo "v4" >> test.txt
❯ git status
位于分支 test
要提交的变更:
  (使用 "git restore --staged <文件>..." 以取消暂存)
        修改:     test.txt

尚未暂存以备提交的变更:
  (使用 "git add <文件>..." 更新要提交的内容)
  (使用 "git restore <文件>..." 丢弃工作区的改动)
        修改:     test.txt

❯ git diff
diff --git a/test.txt b/test.txt
index 4b1d4d4..ed7ce12 100644
--- a/test.txt
+++ b/test.txt
@@ -1,3 +1,4 @@
 v1
 v2
 v3
+v4
❯ git diff --staged
diff --git a/test.txt b/test.txt
index 2139d8b..4b1d4d4 100644
--- a/test.txt
+++ b/test.txt
@@ -1,2 +1,3 @@
 v1
 v2
+v3

对于每一次修改,可以先 git add test.txt 将文件添加到暂存区。注意,这里是加入暂存区而非直接 commit,相当于将真正想提交的放进去,等到都处理好之后再 commit 创建新版本。对应的,git status 可以查看工作区的哪些修改已经放入暂存区、哪些修改还没放入暂存区。类似的,git diff 可以查看工作区和暂存区的变化,加上 --staged 参数可以查看当前暂存区和上一次 commit 的变化。

❯ git commit -m "add v3"
[test d671dea] add v3
 1 file changed, 1 insertion(+)
❯ git status
位于分支 test
尚未暂存以备提交的变更:
  (使用 "git add <文件>..." 更新要提交的内容)
  (使用 "git restore <文件>..." 丢弃工作区的改动)
        修改:     test.txt

修改尚未加入提交(使用 "git add" 和/或 "git commit -a")
❯ git log  
commit d671dea1ee4b9eca423d3918501be21435e9190a (HEAD -> test)  
Author: Bluuue <xxx@qq.com>  
Date:   Fri Sep 11 00:15:50 2026 +0800  
  
   add v3  
  
commit da9d3e62f69dd0fad281bf057a6565d0bab9f4b6  
Author: Bluuue <xxx@qq.com>  
Date:   Thu Sep 10 00:22:32 2026 +0800  
  
   v2  
  
commit e89f78f894b4e2b79ad951c2476245a02ce9dd93 (master)  
Author: Bluuue <xxx@qq.com>  
Date:   Thu Sep 10 00:21:53 2026 +0800  
  
   v1

commit 时,可以使用 -m 参数添加更改说明。在 commit 多次之后,可以通过 git log 查看历史提交记录。此外,--graph 参数可以图形化查看提交记录的关系,-p 参数可以查看每次提交发生的改变。

分支

❯ git switch -c myTest
切换到一个新分支 'myTest'
❯ git branch dev
❯ git switch dev
M       test.txt
切换到分支 'dev'
❯ git status
位于分支 dev
尚未暂存以备提交的变更:
  (使用 "git add <文件>..." 更新要提交的内容)
  (使用 "git restore <文件>..." 丢弃工作区的改动)
        修改:     test.txt

修改尚未加入提交(使用 "git add" 和/或 "git commit -a")

如果想要创建一个新的分支,branch xxxswitch -c xxx 都是可选项。此时有几个概念,首先,分支并不是完全复制一份代码,可以理解为一个指向某个 commit 的指针。之后,HEAD 是一个会指向当前所处分支的指针。此时在 commit 的时候,分支指针就会指向当前分支最新的一次 commit。

❯ git switch myTest
切换到分支 'myTest'
❯ vim test.txt
❯ cat test.txt
v1
v3
v5
❯ git switch dev
错误:您对下列文件的本地修改将被检出操作覆盖:
        test.txt
请在切换分支前提交或储藏您的修改。
正在中止
❯ git add test.txt   
❯ git commit -m "erase v2 and add v5"  
[myTest 0bcd884] erase v2 and add v5  
1 file changed, 1 insertion(+), 1 deletion(-)  
❯ git switch dev  
切换到分支 'dev'  
❯ cat test.txt  
v1  
v2  
v3  
v4
❯ git log --graph --all  
* commit 0bcd884050f9009aa8802e42799d85677be3ea51 (myTest)  
| Author: Bluuue <3531918730@qq.com>  
| Date:   Fri Sep 11 00:41:45 2026 +0800  
|    
|     erase v2 and add v5  
|      
| * commit 08865f73efd372d88b552fbcfd555d33858b7437 (HEAD -> dev)  
|/  Author: Bluuue <3531918730@qq.com>  
|   Date:   Fri Sep 11 00:37:53 2026 +0800  
|      
|       add v4  
|    
* commit d671dea1ee4b9eca423d3918501be21435e9190a (test)  
| Author: Bluuue <3531918730@qq.com>  
| Date:   Fri Sep 11 00:15:50 2026 +0800  
|    
|     add v3  
|    
* commit da9d3e62f69dd0fad281bf057a6565d0bab9f4b6  
| Author: Bluuue <3531918730@qq.com>  
| Date:   Thu Sep 10 00:22:32 2026 +0800  
|    
|     v2  
|    
* commit e89f78f894b4e2b79ad951c2476245a02ce9dd93 (master)  
 Author: Bluuue <3531918730@qq.com>  
 Date:   Thu Sep 10 00:21:53 2026 +0800  
    
     v1

注意,当在当前版本修改文件后,如果立刻切换到别的分支,此时由于会将该修改覆盖掉,所以发生报错,需要 commit 之后再切换。

❯ echo "unfinished optimization" >> test.txt  
❯ git status  
位于分支 dev  
您的分支与上游分支 'origin/dev' 一致。  
  
尚未暂存以备提交的变更:  
 (使用 "git add <文件>..." 更新要提交的内容)  
 (使用 "git restore <文件>..." 丢弃工作区的改动)  
       修改:     test.txt  
  
修改尚未加入提交(使用 "git add" 和/或 "git commit -a")  
❯ git diff  
diff --git a/test.txt b/test.txt  
index 11fea7d..11f3a00 100644  
--- a/test.txt  
+++ b/test.txt  
@@ -4,3 +4,4 @@ v4  
v5  
   
v6  
+unfinished optimization  
❯ git switch myTest  
错误:您对下列文件的本地修改将被检出操作覆盖:  
       test.txt  
请在切换分支前提交或储藏您的修改。  
正在中止  
❯ git stash  
保存工作目录和索引状态 WIP on dev: bc4b307 add peer change  
❯ git status  
位于分支 dev  
您的分支与上游分支 'origin/dev' 一致。  
  
无文件要提交,工作区干净  
❯ cat test.txt  
v1  
v3  
v4  
v5  
  
v6  
❯ git stash list  
stash@{0}: WIP on dev: bc4b307 add peer change  
❯ git switch myTest  
切换到分支 'myTest'  
❯ git switch dev  
切换到分支 'dev'  
您的分支与上游分支 'origin/dev' 一致。  
❯ git stash pop  
位于分支 dev  
您的分支与上游分支 'origin/dev' 一致。  
  
尚未暂存以备提交的变更:  
 (使用 "git add <文件>..." 更新要提交的内容)  
 (使用 "git restore <文件>..." 丢弃工作区的改动)  
       修改:     test.txt  
  
修改尚未加入提交(使用 "git add" 和/或 "git commit -a")  
丢弃了 refs/stash@{0}(dcae8a93f4c8650a11912c3673cfeeddff610cf1)  
❯ cat test.txt  
v1  
v3  
v4  
v5  
  
v6  
unfinished optimization  
❯ git stash list

如果我在代码写了一半的时候想切出去,就可以使用 git stash 临时把修改收起来,先让工作区恢复干净,等到切换回来的时候从 list 里面 git stash pop 即可。注意,pop 后这条记录会消失,如果想保留可以 git stash apply。实际中,还可以 git stash push -m "xxx" 像 commit 一样添加 message,这样更方便识别。

合并

❯ git merge myTest  
自动合并 test.txt  
冲突(内容):合并冲突于 test.txt  
自动合并失败,修正冲突然后提交修正的结果。  
❯ cat test.txt  
v1  
v3  
<<<<<<< HEAD  
v4  
=======  
v5  
>>>>>>> myTest  
❯ vim test.txt  
❯ cat test.txt  
v1  
v3  
v4  
v5 
❯ git status  
位于分支 dev  
您有尚未合并的路径。  
 (解决冲突并运行 "git commit")  
 (使用 "git merge --abort" 中止合并)  
  
未合并的路径:  
 (使用 "git add <文件>..." 标记解决方案)  
       双方修改:   test.txt  
  
修改尚未加入提交(使用 "git add" 和/或 "git commit -a")  
❯ git diff  
diff --cc test.txt  
index ed7ce12,05c4b7c..0000000  
--- a/test.txt  
+++ b/test.txt  
@@@ -1,4 -1,3 +1,5 @@@  
 v1  
- v2  
 v3  
+v4  
+ v5  
++  
❯ git add test.txt  
❯ git status  
位于分支 dev  
所有冲突已解决但您仍处于合并中。  
 (使用 "git commit" 结束合并)  
  
要提交的变更:  
       修改:     test.txt  
  
❯ git commit -m "merge myTest and dev"  
[dev b6af65f] merge myTest and dev  
❯ git log --graph --all  
*   commit b6af65f6327e2318df2497e531b259c22dfa7b86 (HEAD -> dev)  
|\  Merge: 08865f7 0bcd884  
| | Author: Bluuue <3531918730@qq.com>  
| | Date:   Fri Sep 11 00:50:36 2026 +0800  
| |    
| |     merge myTest and dev  
| |    
| * commit 0bcd884050f9009aa8802e42799d85677be3ea51 (myTest)  
| | Author: Bluuue <3531918730@qq.com>  
| | Date:   Fri Sep 11 00:41:45 2026 +0800  
| |    
| |     erase v2 and add v5  
| |    
* | commit 08865f73efd372d88b552fbcfd555d33858b7437  
|/  Author: Bluuue <3531918730@qq.com>  
|   Date:   Fri Sep 11 00:37:53 2026 +0800  
|      
|       add v4  
|    
* commit d671dea1ee4b9eca423d3918501be21435e9190a (test)  
| Author: Bluuue <3531918730@qq.com>  
| Date:   Fri Sep 11 00:15:50 2026 +0800  
|    
|     add v3  
|    
* commit da9d3e62f69dd0fad281bf057a6565d0bab9f4b6  
| Author: Bluuue <3531918730@qq.com>  
| Date:   Thu Sep 10 00:22:32 2026 +0800  
|    
|     v2  
|    
* commit e89f78f894b4e2b79ad951c2476245a02ce9dd93 (master)  
 Author: Bluuue <3531918730@qq.com>  
 Date:   Thu Sep 10 00:21:53 2026 +0800

如果想要合并两个版本,就可以使用 git merge。注意,此时由于 dev 版本里最后是 v4,myTest 版本里最后是 v5,所以 git 无法自动判断如何 merge。此时可以看到 test.txt 里发生了变化,HEAD 下方表示是当前版本的内容,myTest 上方是要合并的版本的内容。此时就可以手动删除后手动 add 然后 commit 来完成 merge。

❯ git branch  
* dev  
 master  
 myTest  
 test  
❯ git branch -d test  
已删除分支 test(原为 d671dea)。  
❯ git branch  
* dev  
 master  
 myTest  
❯ git log --graph --all  
*   commit b6af65f6327e2318df2497e531b259c22dfa7b86 (HEAD -> dev)  
|\  Merge: 08865f7 0bcd884  
| | Author: Bluuue <3531918730@qq.com>  
| | Date:   Fri Sep 11 00:50:36 2026 +0800  
| |    
| |     merge myTest and dev  
| |    
| * commit 0bcd884050f9009aa8802e42799d85677be3ea51 (myTest)  
| | Author: Bluuue <3531918730@qq.com>  
| | Date:   Fri Sep 11 00:41:45 2026 +0800  
| |    
| |     erase v2 and add v5  
| |    
* | commit 08865f73efd372d88b552fbcfd555d33858b7437  
|/  Author: Bluuue <3531918730@qq.com>  
|   Date:   Fri Sep 11 00:37:53 2026 +0800  
|      
|       add v4  
|    
* commit d671dea1ee4b9eca423d3918501be21435e9190a  
| Author: Bluuue <3531918730@qq.com>  
| Date:   Fri Sep 11 00:15:50 2026 +0800  
|    
|     add v3  
|    
* commit da9d3e62f69dd0fad281bf057a6565d0bab9f4b6  
| Author: Bluuue <3531918730@qq.com>  
| Date:   Thu Sep 10 00:22:32 2026 +0800  
|    
|     v2  
|    
* commit e89f78f894b4e2b79ad951c2476245a02ce9dd93 (master)  
 Author: Bluuue <3531918730@qq.com>  
 Date:   Thu Sep 10 00:21:53 2026 +0800

当然,可以直接 branch 查看当前所有分支,-d 参数来删除某个分支。注意,此时不会改变历史版本记录,能改变的只有 branch,所有的 commit 都是不可改变的!

高级命令

远程仓库

❯ git init --bare git-demo-remote.git
在 /home/Bluuue/Desktop/git-demo-remote.git/ 初始化空的 Git 仓库
❯ ls git-demo-remote.git  
hooks  info  objects  refs  config  description  HEAD  

❯ git remote add origin ../git-demo-remote.git  
❯ git remote -v  
origin  ../git-demo-remote.git (fetch)  
origin  ../git-demo-remote.git (push)

❯ git push -u origin dev  
枚举对象中: 18, 完成.  
对象计数中: 100% (18/18), 完成.  
使用 32 个线程进行压缩  
压缩对象中: 100% (6/6), 完成.  
写入对象中: 100% (18/18), 1.21 KiB | 1.21 MiB/s, 完成.  
总共 18(差异 1),复用 0(差异 0),包复用 0(来自 0 个包)  
To ../git-demo-remote.git  
* [new branch]      dev -> dev  
分支 'dev' 设置为跟踪 'origin/dev'。  
❯ git branch -a  
* dev  
 master  
 myTest  
 remotes/origin/dev
 ❯ git branch -vv  
* dev    b6af65f [origin/dev] merge myTest and dev  
 master e89f78f v1  
 myTest 0bcd884 erase v2 and add v5

remote 可以理解为给另一个 git 仓库保存一个名字和地址,然后管理当前仓库关联的其他仓库,在这里名字就是 origin。在创建一个新仓库并添加之后,此时就可以将当前分支 push 到远程仓库。此时可以看到分支中多了一个 remotes/origin/dev,这个表示上一次和这个远程仓库联系时的 dev 状态。

对于 git push -u origin dev 命令,含义就是更新远程仓库的状态,将缺少的 commit 发过去并更新 branch 的指向。其中,-u 参数是将本地的 dev 的 upstream 设置为 origin/dev,默认以后两者对应,于是之后在 dev 分支时直接 git push 就可以更新远程的这个分支了。此时可以使用 git branch -vv 查看分支的 upstream。

❯ echo "v6" >> test.txt  
❯ git add test.txt  
❯ git commit -m "add v6"  
[dev 808cb24] add v6  
1 file changed, 1 insertion(+)  
❯ git status  
位于分支 dev  
您的分支领先 'origin/dev' 共 1 个提交。  
 (使用 "git push" 来发布您的本地提交)  
  
无文件要提交,工作区干净
❯ git push  
枚举对象中: 5, 完成.  
对象计数中: 100% (5/5), 完成.  
写入对象中: 100% (3/3), 248 字节 | 248.00 KiB/s, 完成.  
总共 3(差异 0),复用 0(差异 0),包复用 0(来自 0 个包)  
To ../git-demo-remote.git  
  b6af65f..808cb24  dev -> dev  
❯ git status  
位于分支 dev  
您的分支与上游分支 'origin/dev' 一致。  
  
无文件要提交,工作区干净  
❯ git log --oneline --graph --decorate --all  
* 808cb24 (HEAD -> dev, origin/dev) add v6  
*   b6af65f merge myTest and dev  
|\     
| * 0bcd884 (myTest) erase v2 and add v5  
* | 08865f7 add v4  
|/     
* d671dea add v3  
* da9d3e6 v2  
* e89f78f (master) v1  

在进行了一次 commit 之后,此时就存在一个 “领先 ahead” 的概念,意思就是本地的 dev 比上次联系时远程仓库的 dev 多了一个 commit。 此时再 push 可以发现 commit 同步过去了,提示也说明了这一点。

❯ git fetch origin  
remote: 枚举对象中: 4, 完成.  
remote: 对象计数中: 100% (4/4), 完成.  
remote: 压缩对象中: 100% (2/2), 完成.  
remote: 总共 3(差异 0),复用 0(差异 0),包复用 0(来自 0 个包)  
展开对象中: 100% (3/3), 269 字节 | 269.00 KiB/s, 完成.  
来自 ../git-demo-remote  
  808cb24..bc4b307  dev        -> origin/dev  
❯ git log --oneline --graph --decorate --all  
* bc4b307 (origin/dev) add peer change  
* 808cb24 (HEAD -> dev) add v6  
*   b6af65f merge myTest and dev  
|\     
| * 0bcd884 (myTest) erase v2 and add v5  
* | 08865f7 add v4  
|/     
* d671dea add v3  
* da9d3e6 v2  
* e89f78f (master) v1
❯ git merge origin/dev  
更新 808cb24..bc4b307  
Fast-forward  
peer.txt | 1 +  
1 file changed, 1 insertion(+)  
create mode 100644 peer.txt  
❯ ls  
peer.txt  test.txt

当有别的远程开发者更新了仓库中的分支,此时本地的分支仍然指向之前的 commit。此时就可以通过 git fetch origin 来将仓库中的内容同步到本地,注意此时不会改变本地 dev 的指向,只会更新其对应的 origin/dev 状态。之后就可以使用 git loggit diff 查看发生了什么变化,如果想要将修改放进 dev 的话,merge 一下即可。

当然,如果觉得先 fetch 再 merge 太麻烦,可以直接 git pull。而当本地压根没有这个仓库时,就是 git clone xxx 将远程仓库直接搬下来。

撤销

❯ echo "temporary change" >> test.txt  
❯ git diff  
diff --git a/test.txt b/test.txt  
index 11fea7d..51b434a 100644  
--- a/test.txt  
+++ b/test.txt  
@@ -4,3 +4,4 @@ v4  
v5  
   
v6  
+temporary change  
❯ cat test.txt  
v1  
v3  
v4  
v5  
  
v6  
temporary change  
❯ git restore test.txt  
❯ git diff

在修改当前工作区的内容后,如果不想要了,可以 git restore xxx 进行撤销。具体原理是,将当前暂存区的该文件拿来覆盖掉工作区的文件,也就是丢弃尚未暂存的文件。

❯ echo "temporary change" >> test.txt  
❯ git add test.txt  
❯ git status  
位于分支 dev  
您的分支与上游分支 'origin/dev' 一致。  
  
要提交的变更:  
 (使用 "git restore --staged <文件>..." 以取消暂存)  
       修改:     test.txt  
  
❯ git restore --staged test.txt  
❯ git status  
位于分支 dev  
您的分支与上游分支 'origin/dev' 一致。  
  
尚未暂存以备提交的变更:  
 (使用 "git add <文件>..." 更新要提交的内容)  
 (使用 "git restore <文件>..." 丢弃工作区的改动)  
       修改:     test.txt  
  
修改尚未加入提交(使用 "git add" 和/或 "git commit -a")

而如果想丢弃暂存区的内容,可以使用 --staged 参数来取消暂存,就是 git add 的反向操作。

❯ git switch -c amend-demo  
切换到一个新分支 'amend-demo'  
❯ echo "amend demo" > amend.txt  
❯ git add amend.txt  
❯ git commit -m "add amend deom"  
[amend-demo f030ac8] add amend deom  
1 file changed, 1 insertion(+)  
create mode 100644 amend.txt
❯ git log -1  
commit f030ac810b951ecbe843cf4a164fc9366bfa78d9 (HEAD -> amend-demo)  
Author: Bluuue <3531918730@qq.com>  
Date:   Sun Sep 13 19:27:05 2026 +0800  
  
   add amend deom  
❯ git commit --amend -m "add amend demo"  
[amend-demo a41cbdc] add amend demo  
Date: Sun Sep 13 19:27:05 2026 +0800  
1 file changed, 1 insertion(+)  
create mode 100644 amend.txt  
❯ git log -1  
commit a41cbdc1c97d5f453d1f747cf92240ad63edaac7 (HEAD -> amend-demo)  
Author: Bluuue <3531918730@qq.com>  
Date:   Sun Sep 13 19:27:05 2026 +0800  
  
   add amend demo
   
❯ echo "forget file" > forget.txt  
❯ git add forget.txt  
❯ git commit --amend --no-edit  
[amend-demo e3f857b] add amend demo  
Date: Sun Sep 13 19:27:05 2026 +0800  
2 files changed, 2 insertions(+)  
create mode 100644 amend.txt  
create mode 100644 forget.txt  
❯ git show HEAD  
commit e3f857bc45a3b26f2b73c1200de95bf3bc46d73a (HEAD -> amend-demo)  
Author: Bluuue <3531918730@qq.com>  
Date:   Sun Sep 13 19:27:05 2026 +0800  
  
   add amend demo  
  
diff --git a/amend.txt b/amend.txt  
new file mode 100644  
index 0000000..bf94862  
--- /dev/null  
+++ b/amend.txt  
@@ -0,0 +1 @@  
+amend demo  
diff --git a/forget.txt b/forget.txt  
new file mode 100644  
index 0000000..4adede8  
--- /dev/null  
+++ b/forget.txt  
@@ -0,0 +1 @@  
+forget file

而当 commit 完才发现 commit message 写错了,此时使用 --amend 可以修改 message。不仅如此,如果发现漏了一个文件没提交,此时可以将这个文件 add 到暂存区,然后使用 --amend 参数重新 commit。注意看,两个 commit 的哈希值不同,说明并不是在之前的基础上修改,而是相当于换了一个新的。这里的 --no-edit 表示不修改原来的 message,而 git show 可以查看某个 commit 的内容和带来的修改。

❯ git log -2  
commit 7becc5ab17afbac3ab52638b8a06569eb4376923 (HEAD -> reset-demo)  
Author: Bluuue <3531918730@qq.com>  
Date:   Sun Sep 13 19:59:41 2026 +0800  
  
   add r2  
  
commit 61be045f28bd86cf36d060277b91132b0e5b4d9c  
Author: Bluuue <3531918730@qq.com>  
Date:   Sun Sep 13 19:53:16 2026 +0800  
  
   add r1
   
❯ git reset --soft HEAD~1  
❯ git status  
位于分支 reset-demo  
要提交的变更:  
 (使用 "git restore --staged <文件>..." 以取消暂存)  
       修改:     reset.txt  
  
❯ git diff --staged  
diff --git a/reset.txt b/reset.txt  
index f7d55cf..e573969 100644  
--- a/reset.txt  
+++ b/reset.txt  
@@ -1 +1,2 @@  
r1  
+r2

❯ git reset HEAD~1  
重置后取消暂存的变更:  
M       reset.txt  
❯ git status  
位于分支 reset-demo  
尚未暂存以备提交的变更:  
 (使用 "git add <文件>..." 更新要提交的内容)  
 (使用 "git restore <文件>..." 丢弃工作区的改动)  
       修改:     reset.txt  
  
修改尚未加入提交(使用 "git add" 和/或 "git commit -a")  
❯ git diff  
diff --git a/reset.txt b/reset.txt  
index f7d55cf..e573969 100644  
--- a/reset.txt  
+++ b/reset.txt  
@@ -1 +1,2 @@  
r1  
+r2
❯ cat reset.txt  
r1  
r2  

❯ git reset --hard HEAD~1  
HEAD 现在位于 61be045 add r1  
❯ cat reset.txt  
r1

这个就有点复杂了。首先,git reset 可以把当前 branch 的指针移动到指定 commit。默认模式是 --mixed,即撤销 commit 和暂存区的内容,但仍然保留工作区的内容。而 --soft 是只撤销 commit 而不撤销暂存区的内容,--hard 是连带工作区的内容也不要了,是比较危险的行为。

❯ echo "normal code" > revert.txt  
❯ git add revert.txt  
❯ git commit -m "add normal code"  
[revert-demo 915b4b0] add normal code  
1 file changed, 1 insertion(+)  
create mode 100644 revert.txt  
❯ echo "BAD CHANGE" >> revert.txt  
❯ git add revert.txt  
❯ git commit -m "add bad code"  
[revert-demo df4832b] add bad code  
1 file changed, 1 insertion(+)  
❯ cat revert.txt  
normal code  
BAD CHANGE  
❯ git commit --amend -m "add bad change"  
[revert-demo 2c84a8f] add bad change  
Date: Sun Sep 13 20:16:58 2026 +0800  
1 file changed, 1 insertion(+)  
❯ git revert HEAD --no-edit  
[revert-demo 31991db] Revert "add bad change"  
Date: Sun Sep 13 20:17:39 2026 +0800  
1 file changed, 1 deletion(-)  
❯ cat revert.txt  
normal code  
❯ git log --graph -4  
* commit 31991db60cb4b13a579de09df99efef905c07c72 (HEAD -> revert-demo)  
| Author: Bluuue <3531918730@qq.com>  
| Date:   Sun Sep 13 20:17:39 2026 +0800  
|    
|     Revert "add bad change"  
|        
|     This reverts commit 2c84a8fe31912f8e0d5ea8c830039b1511999ab8.  
|    
* commit 2c84a8fe31912f8e0d5ea8c830039b1511999ab8  
| Author: Bluuue <3531918730@qq.com>  
| Date:   Sun Sep 13 20:16:58 2026 +0800  
|    
|     add bad change  
|    
* commit 915b4b083721f8b4e91bd44f6304d6844c800d22  
| Author: Bluuue <3531918730@qq.com>  
| Date:   Sun Sep 13 20:16:42 2026 +0800  
|    
|     add normal code  
|    
* commit 61be045f28bd86cf36d060277b91132b0e5b4d9c (reset-demo)  
| Author: Bluuue <3531918730@qq.com>  
| Date:   Sun Sep 13 19:53:16 2026 +0800  
|    
|     add r1

这个就比较牛了。和 reset 不同,git revert 不会删除一个 commit,而是在之前的基础上尝试撤销某个历史 commit 的影响。对于已经 push 的 commit,直接 reset 会导致协作历史很混乱,而 revert 可以不改变协作历史,只是加入一个新的 commit。当然,既然是撤销历史影响,那么肯定会像 merge 一样发生冲突,那么就需要手动操作了。

重建

❯ echo "main change" > rebase-main.txt  
❯ git add rebase-main.txt  
❯ git commit -m "add main change"  
[rebase-main 758c963] add main change  
1 file changed, 1 insertion(+)  
create mode 100644 rebase-main.txt  
❯ git switch -c rebase-feature dev  
切换到一个新分支 'rebase-feature'  
❯ echo "feature v1" > rebase-feature.txt  
❯ git add rebase-feature.txt  
❯ git commit -m "add feature v1"  
[rebase-feature b1ce59d] add feature v1  
1 file changed, 1 insertion(+)  
create mode 100644 rebase-feature.txt  
❯ echo "feature v2" >> rebase-feature.txt  
❯ git add rebase-feature.txt  
❯ git commit -m "add feature v1"  
[rebase-feature f5e7ce2] add feature v1  
1 file changed, 1 insertion(+)
❯ git branch rebase-feature-old  
❯ git rebase rebase-main  
成功变基并更新 refs/heads/rebase-feature。  
❯ git log --oneline --graph --decorate --all  
* 24bbf60 (HEAD -> rebase-feature) add feature v1  
* 4535277 add feature v1  
* 758c963 (rebase-main) add main change  
* 31991db (revert-demo) Revert "add bad change"  
* 2c84a8f add bad change  
* 915b4b0 add normal code  
* 61be045 (reset-demo) add r1  
* e3f857b (amend-demo) add amend demo  
| * f5e7ce2 (rebase-feature-old) add feature v1  
| * b1ce59d add feature v1  
|/     
* bc4b307 (origin/dev, dev) add peer change  
* 808cb24 add v6  
*   b6af65f merge myTest and dev  
|\     
| * 0bcd884 (myTest) erase v2 and add v5  
* | 08865f7 add v4  
|/     
* d671dea add v3  
* da9d3e6 v2  
* e89f78f (master) v1

当从某个 commit 开始分出去一个 branch,分别开发 branch 和 main 两条线。此时如果想让我的 branch 看起来像从最新 main 开始开发一样,就可以 git rebase main。这样会从 main 所指的 commit 开始,将当前 branch 的 commit 创建重新应用一遍,表现为在 main 的基础上创建多个 commit。

除此之外,如果只想把某个特定的 commit 搬过来,可以使用 git cherry-pick xxx

.gitignore

这个文件的用处就是,某些本来没有被跟踪的文件,不要老是在 git status 里提醒我,也不要误加进仓库。比如要提交的只有 main.cpp,其的生成文件 main 是不需要提交的,就可以在 .gitignore 里写上 main,或者直接 *.log

二分

❯ echo "OK" > state.txt  
❯ git add state.txt  
❯ git commit -m "bisect: state 1"  
[bisect-demo 6a94d1d] bisect: state 1  
1 file changed, 1 insertion(+)  
create mode 100644 state.txt  
❯ echo "version 2" >> state.txt  
❯ git add state.txt  
❯ git commit -m "bisect: state 2"  
[bisect-demo 1fe77c5] bisect: state 2  
1 file changed, 1 insertion(+)  
❯ echo "version 3" >> state.txt  
❯ git add state.txt  
❯ git commit -m "bisect: state 3"  
[bisect-demo 4e67559] bisect: state 3  
1 file changed, 1 insertion(+)  

❯ echo "BROKEN" >| state.txt  
❯ echo "version 4" >> state.txt  
❯ git add state.txt  
❯ git commit -m "bisect: introduce bug"  
[bisect-demo 12b6c15] bisect: introduce bug  
1 file changed, 2 insertions(+), 3 deletions(-)  
❯ echo "version 5" >> state.txt  
❯ git add state.txt  
❯ git commit -m "bisect: state 5"  
[bisect-demo 63251d0] bisect: state 5  
1 file changed, 1 insertion(+), 1 deletion(-)  
❯ echo "version 6" >> state.txt  
❯ git add state.txt  
❯ git commit -m "bisect: state 6"  
[bisect-demo a4af14c] bisect: state 6  
1 file changed, 1 insertion(+)  

❯ git log --oneline --graph -6  
* a4af14c (HEAD -> bisect-demo) bisect: state 6  
* 63251d0 bisect: state 5  
* 12b6c15 bisect: introduce bug  
* 98af541 bisect: state 3  
* 5220b18 bisect: state 2  
* 6a94d1d bisect: state 1
  
❯ git bisect start  
状态:正在等待 'good' 和 'bad' 提交  
❯ git bisect bad  
状态:正在等待 'good' 提交,已知 'bad' 提交  
❯ git bisect good 6a94d1d  
二分查找中:在此之后,还剩 2 个版本待测试 (大概 1 步)  
[98af541a3b6c39a564684bfcc2475ac1bf110f91] bisect: state 3  
❯ git bisect reset  
之前的 HEAD 位置是 98af541 bisect: state 3  
切换到分支 'bisect-demo'

当发现原来程序还是好的而现在程序坏了,但不知道是在之前哪次修改后坏的,此时就可以使用 git bisect 二分查找出现问题的 commit。除此之外,还可以检查在哪次 commit 后性能出现了严重问题。

使用时,只需要 start 之后说明 good 和 bad 的 commit,之后就会自动返回分界 commit。注意,最后要 reset 返回开始前的位置。除此之外,还可以使用 git bisect run xxx.sh,写一个脚本编译并运行程序,性能达到阈值就返回 0 表示 good 来测试性能。

恢复

❯ git switch -c reflog-demo
切换到一个新分支 'reflog-demo'
❯ echo "very important code" > recover.txt
❯ git add recover.txt
❯ git commit -m "add important code"
[reflog-demo 8a2ad13] add important code
 1 file changed, 1 insertion(+)
 create mode 100644 recover.txt
❯ git log -2 --oneline
8a2ad13 (HEAD -> reflog-demo) add important code
bc4b307 (origin/dev, dev) add peer change
❯ git reset --hard HEAD~1
HEAD 现在位于 bc4b307 add peer change
❯ git log -2 --oneline
bc4b307 (HEAD -> reflog-demo, origin/dev, dev) add peer change
808cb24 add v6
❯ git reflog
bc4b307 (HEAD -> reflog-demo, origin/dev, dev) HEAD@{0}: reset: moving to HEAD~1
8a2ad13 HEAD@{1}: commit: add important code
...
❯ git branch recovered HEAD@{1}

如果 reset 后发现操作错了,此时可以通过 git reflog 查看 HEAD 之前的位置并恢复过去。

总结

这个功能也太强了!

END

posted @ 2026-09-13 22:19  WBluuue  阅读(10)  评论(0)    收藏  举报