前言
本文档您将学习如何使用 Git 进行版本回退以及日志方面的查看。
commit id
在版本回退之前,您需要知道 commit id,commit id 指的是每一次提交之后,都会有一个 ID 号作为标识,例如我这三次的提交:
PS > cd E:\git-test\
PS > git log
commit 47d9368938c852c99931df28d18925273c8c19b1 (HEAD -> master)
Author: xxxxx
Date: Sat Mar 1 12:56:36 2025 +0800
tmp-file.txt --> Tmp-File.txt
commit ae6cc9de54f827c652df0002d7fa563bd4e19016
Author: xxxxx
Date: Sat Mar 1 12:44:38 2025 +0800
First commit
commit 9a703559413238e09c1c1a4fb6221e370a572247
Author: xxxxx
Date: Sat Mar 1 11:06:43 2025 +0800
测试文件
git log
git log
是一个强大的命令,语法为:
git log [<options>] [<revision-range>] [[--] <path>…]
常用选项有:
--oneline
– 单行显示 commit id 以及提交的说明-p
– 以补丁的方式显示每次更新的差异性--stat
– 显示每次提交后增减行数的统计信息--no-merges
– 显示整个提交的历史记录,但跳过合并过的记录
按照特定条件筛选日志内容
-
按照日期时间的先后顺序
--before=<date>
– 显示特定日期时间之前的提交,例如git log --before="2025-03-09 13:20:30"
--after=<date>
– 显示特定日期时间之后的提交
选项值 date 除了支持自然日期时间之外,还支持一些其他格式,如相对时间(比如 "2 hours ago","2 weeks ago")、以秒为单位的 UNIX 时间戳等,但比较少用到,使用自然日期时间即可。
-
按照显示的数量
-n <number>
– 显示最近的 n 条记录
-
按照作者
--author=<pattern>
– 按照正则表达式筛选作者。比如git log --author="Frank Lee | Jack Chen" -E
--regexp-ignore-case
– 忽略正则表达式中的大小写-E
– 启用扩展正则表达式
默认情况下,
--author=<pattern>
选项使用 BRE(basic regular express,基础正则表达式),若要使用 ERE(extend regular express,扩展正则表达式),请再添加-E
选项。 -
按照提交的信息
--grep=<pattern>
– 按照正则表达式筛选提交信息--regexp-ignore-case
– 忽略正则表达式中的大小写-E
– 启用扩展正则表达式
默认情况下,
--grep=<pattern>
选项使用 BRE(basic regular express,基础正则表达式),若要使用 ERE(extend regular express,扩展正则表达式),请再添加-E
选项。 -
按照特定的文件或目录
众所周知,在 bash 当中,可使用
--
表示某一个命令选项的结束,同样的的规则也适用于git log
,比如:# 查看 public 目录下的所有日志信息 Shell > git log -- public/ # 也可以针对一个或多个文件 Shell > git log -- tmp.text README.md
-
按照分支
分支我们还没有说明,分为本地分支与远程分支。比如:
# 查看本地分支 master 的日志信息 Shell > git log master # 也可以同时指定多个分支 Shell > git log master dev ## 或者 Shell > git log --branches=master,dev # 分支之间的对比 ## 反向比较:存在于 dev 分支中但还没有合并到 master 的提交记录,即显示 dev 分支比 master 分支多出的提交记录 Shell > git log master..dev ## 对称比较:三个 "..." 表示两个分支不重叠的所有提交(即两个分支的独有提交) Shell > git log master...dev
-
按照增减文件内容的字符串
-S <string>
– 比如git log -S "First Line"
,这非常适合那些需要精准定位修改时是哪一次 commit 的--pickaxe-regex
– 将给定的 \<string>视为扩展的 POSIX 正则表达式以进行匹配
-
按照正则表达式匹配文件增减内容的字符串
-G <regex>
--regexp-ignore-case
– 忽略正则表达式中的大小写-E
– 启用扩展正则表达式
我们可以通过以下例子来看 -S 和 -G 之间的区别:
PS > cd E:\git-test\ PS > new-Item lab-file.txt # PowerShell 中的换行符为 `n PS > echo "hit = frotz(nitfol, mf2.ptr, 1, 0);" > lab-file.txt PS > git add ./ PS > git commit -m "lab-file初始内容" # 将文件中的该行删除并使用以下的文本替换掉: PS > echo "return frotz(nitfol, two->ptr, 1, 0);" > .\lab-file.txt PS > git add ./ PS > git commit -m "修改后的lab-file文件" PS > git log -p commit 8a77771a1c837c9e4a06014af7a4801fd982260c (HEAD -> master) Author: xxxxx Date: Sun Mar 9 18:41:08 2025 +0800 修改后的lab-file文件 diff --git a/lab-file.txt b/lab-file.txt index 5516f96..f14d785 100644 --- a/lab-file.txt +++ b/lab-file.txt @@ -1 +1 @@ -hit = frotz(nitfol, mf2.ptr, 1, 0); +return frotz(nitfol, two->ptr, 1, 0); ... # 若使用 -G,这两次的 commit 都会正常出现 PS > git log -G "frotz\(nitfol" --oneline 8a77771 (HEAD -> master) 修改后的lab-file文件 f20abbc lab-file初始内容 # 但若使用 -S 的正则表达式,则只会有上一次提交记录被筛选出来 ## 为什么呢?因为 frotz(nitfol 这个字符串的出现次数没有变化,修改之前是1次,修改之后还是1次 PS > git log -S "frotz\(nitfol" --pickaxe-regex --oneline f20abbc lab-file初始内容
如下说明:
选项 说明 正则表达式 -G
在文件内容变更前后满足正则表达式即可 原生支持 -S
在文件内容变更前后需要满足字符串的次数变化,若匹配的字符串在修改前后无次数变化,则不会筛选出具体的日志 需要 –pickaxe-regex
以上的筛选条件可以组合使用,但需要注意范围的由大到小。
众所周知,由于历史的发展,正则表达式主要有两大流派:
- POSIX
- BRE(basic regular express,基础正则表达式)
- ERE(extend regular express,扩展正则表达式)
- POSIX character class
- PCRE (Perl Compatible Regular Expressions):在各种编程语言中最常见的。
版本回退
使用命令 git reset --hard <commit-id>
即可,commid-id 可通过 git log
查询到。
PS > git log --oneline
8a77771 (HEAD -> master) 修改后的lab-file文件
f20abbc lab-file初始内容
47d9368 tmp-file.txt --> Tmp-File.txt
ae6cc9d First commit
9a70355 测试文件
假设我需要将版本回退到 lab-file.txt 这个文件出现之前的内容,通过查询可知,我们需要的 commit id 为 47d9368:
PS > git reset --hard 47d9368
HEAD is now at 47d9368 tmp-file.txt --> Tmp-File.txt
PS > ls
Directory: E:\git-test
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a--- 2025/3/1 12:43 12 Tmp-File.txt
一旦进行版本回退,则在此版本之后进行 commit 提交的日志会查询不到:
PS > git log --oneline
47d9368 (HEAD -> master) tmp-file.txt --> Tmp-File.txt
ae6cc9d First commit
9a70355 测试文件
若您还需要查阅它们,可通过以下 git reflog
命令查询 commit id,如下:
PS > git reflog
47d9368 (HEAD -> master) HEAD@{0}: reset: moving to 47d9368
8a77771 HEAD@{1}: commit: 修改后的lab-file文件
f20abbc HEAD@{2}: commit: lab-file初始内容
...
HEAD 动态指针
Git 底层原理当中,其实最重要的就是这个 "动态指针",英文叫HEAD。在正常分支状态下,HEAD 通过分支指针间接指向最新提交。HEAD 是一个特殊指针,其指向的位置会随着分支的切换或提交的变化而变动。
-
正常情况(HEAD 通过分支指针间接指向最新提交)
HEAD(动态指针) │ ▼ [master](分支指针) │ ▼ Commit C3(最新提交)
说明如下:
- HEAD 指向了 master,在磁盘上,指的是项目目录下
.git\HEAD
这个文件,其文件内容为ref: refs/heads/master
。 - 分支指针 master 指向了最新提交,指的是项目目录下
.git/refs/heads/master
这个文件,其文件内容为具体的 commit id (commit id 即哈希值) - 在 C3 上新提交 C4 之后,master 分支指针会更新到 C4,HEAD 仍通过 master 间接指向 C4
- HEAD 指向了 master,在磁盘上,指的是项目目录下
-
分离 HEAD (即 HEAD 直接指向具体的 commit id,Detached HEAD)
若使用者直接将 HEAD 指向 commit id,则会出现一种被称为 Detached HEAD 的情况。以下两种操作方式可进入这种情况:
旧版本的操作:
PS > git checkout <commit-id>
新版本的操作:
PS > git switch --detach <commit-id>
在 Detached HEAD 状态下,你在当前状态下做的任何提交都不会和当前活跃分支关联,这时可选择以下三种方式的其中一种来进行下一步操作:
- 直接在当前提交记录上新创建分支
- 在当前提交记录的基础上再做一些修改并提交然后绑定到分支(如下示例)
- 直接切换分支放弃 Detached HEAD 状态下的修改提交
# 假设不小心将 HEAD 指向了具体的 commit id PS > git switch -d ae6cc9d HEAD is now at ae6cc9d First commit PS > git status HEAD detached at ae6cc9d nothing to commit, working tree clean PS > cat .\tmp-file.txt First Line # 做了一些修改 PS > echo "Second Line" >> .\tmp-file.txt PS > cat .\tmp-file.txt First Line Second Line PS > git add ./ PS > git commit -m "tmp-file文件添加第二行" # 创建新分支指向修改后的提交记录 PS > git branch new-branch PS > git switch new-branch PS > git status On branch new-branch nothing to commit, working tree clean PS > git log --oneline b7354a2 (HEAD -> new-branch) tmp-file文件添加第二行 ae6cc9d First commit 9a70355 测试文件
-
切换到新分支
HEAD 指向新的分支指针,分支指针指向最新的提交,此时 HEAD 依然通过分支指针间接指向最新提交。
Q:上面的例子进行了版本回退,这里的 HAED@{1} 和 HEAD@{2} 是什么意思?
这里的数字表示动态指针相对最近一次移动的上一个位置,0 表示当前指针的位置,1 表示指针上一次指到的位置,以此类推。
