Skip to content

深入了解 Git Log:不只是 git log

Git 官方文档

Git - 查看提交历史

  • git log 是 Git 版本控制系统中用于查看提交历史的命令。它提供了关于项目演进的详细信息,包括每个提交的作者、提交日期、提交信息以及哈希值等。
  • git log是一个常用的命令,用于查看提交历史。然而,除了这个常见的命令之外,Git 还提供了两个强大而有用的日志命令:git shortloggit reflog

git log 显示提交历史

基本用法

最简单的 git log 命令,将显示完整的提交历史,最新的提交在最上方:

bash
$ git log
commit ca82a6dff817ec66f44342007202690a93763949
Author: Scott Chacon <schacon@gee-mail.com>
Date:   Mon Mar 17 21:52:11 2008 -0700

    changed the version number

commit 085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7
Author: Scott Chacon <schacon@gee-mail.com>
Date:   Sat Mar 15 16:40:33 2008 -0700

    removed unnecessary test

commit a11bef06a3f659402fe7563abf99ad00de2209e6
Author: Scott Chacon <schacon@gee-mail.com>
Date:   Sat Mar 15 10:31:28 2008 -0700

    first commit

这将输出一系列提交记录,每个记录包括以下信息:

  • 提交哈希(commit hash): 唯一标识每个提交的 40 个字符的哈希值。
  • 作者(Author): 提交的作者。
  • 日期(Date): 提交的日期和时间。
  • 提交信息(Commit message): 提交时所附的描述信息。

git log 常用选项

选项说明
-p按补丁格式显示每个提交引入的差异。
--stat显示每次提交的文件修改统计信息。
--shortstat只显示 --stat 中最后的行数修改添加移除统计。
--name-only仅在提交信息后显示已修改的文件清单。
--name-status显示新增、修改、删除的文件清单。
--abbrev-commit仅显示 SHA-1 校验和所有 40 个字符中的前几个字符。
--relative-date使用较短的相对时间而不是完整格式显示日期(比如“2 weeks ago”)。
--graph在日志旁以 ASCII 图形显示分支与合并历史。
--pretty使用其他格式显示历史提交信息。可用的选项包括 oneline、short、full、fuller 和 format(用来定义自己的格式)。
--oneline--pretty=oneline --abbrev-commit 合用的简写。

限制 git log 输出的选项

选项说明
-<n>仅显示最近的 n 条提交。
--since, --after仅显示指定时间之后的提交。
--until, --before仅显示指定时间之前的提交。
--author仅显示作者匹配指定字符串的提交。
--committer仅显示提交者匹配指定字符串的提交。
--grep仅显示提交说明中包含指定字符串的提交。
-S仅显示添加或删除内容匹配指定字符串的提交。

自定义输出格式

  • git log --pretty=format 是用于自定义 git log 输出格式的选项。
  • 通过这个选项,你可以定义输出中包含的信息和它们的格式。
选项说明
%H提交的完整哈希值
%h提交的简写哈希值
%T树的完整哈希值
%t树的简写哈希值
%P父提交的完整哈希值
%p父提交的简写哈希值
%an作者名字
%ae作者的电子邮件地址
%ad作者修订日期(可以用 --date=选项 来定制格式)
%ar作者修订日期,按多久以前的方式显示
%cn提交者的名字
%ce提交者的电子邮件地址
%cd提交日期
%cr提交日期(距今多长时间)
%s提交说明

author vs committer

  • 作者 author 指的是实际作出修改的人
  • 提交者 committer 指的是最后将此工作成果提交到仓库的人

案例

简单使用

bash
# 显示最近的三个提交记录
git log -3

# 图像化显示分支的合并和分离
git log --graph

# 按照时间过滤历史记录
git log --since="2022-01-01" --until="2023-01-01"
git log --after="2022-01-01" --before="2023-01-01"

# 按照作者过滤历史记录
git log --author="John Doe"

# 以简洁的一行形式显示每个提交
git log --oneline

# 根据正则表达式过滤历史记录
git log --grep="bug #42"

# 自定义输出格式:显示提交的简写哈希值%h、提交日期%ad、提交说明%s、提交作者%an
git log --format="%h %as %s %an"

# 自定义输出格式:显示提交的简写哈希值%h(红色 %Cred %Creset)、提交日期%ad、提交说明%s(蓝色 %Cblue %Creset)、提交作者%an
git log --format="%Cred%h%Creset %as %Cblue%s%Creset %an"

组合使用

bash
# 查看 lisi 在过去 20 天内的提交记录
git log --author="lisi" --since="20 days ago"

# 查看 lisi 在 2023-11-01 到 2023-12-01 之间所有分支的提交记录,排除 master 分支和 merge 提交
git log --author="lisi" --since="2023-11-01" --until="2023-12-01" --all --not master --no-merges

git shortlog 以提交者为单位的简洁报告

git shortlog命令以提交者为单位,按照字母顺序列出每个提交的摘要。这对于生成类似邮件列表的报告非常有用,可以清晰地显示每个提交的作者和提交信息。

bash
# 查看简洁的提交者报告
git shortlog

此命令的输出类似于以下样例:

bash
zhangsan (3):
      Commit message 1
      Commit message 2

lisi (2):
      Another commit message
      Yet another commit message

每个提交者都被列为一个部分,其中包含他们的提交次数和相应的提交信息。这使得迅速浏览和了解每个贡献者的工作成果变得非常方便。

bash
# 查看 lisi 在 2023-11-01 到 2023-12-01 之间所有分支的 merge 提交,按照 commit 次数倒叙排列
git shortlog --author="lisi" --since="2023-11-01" --until="2023-12-01" --all --merges --numbered

# 查看 lisi 在 2023-11-01 到 2023-12-01 之间所有分支的 merge 提交,按照 commit 次数倒叙排列,只显示提交者名字和提交次数
git shortlog --author="lisi" --since="2023-11-01" --until="2023-12-01" --all --merges --numbered --summary

# 查看 lisi 在 2023-11-01 到 2023-12-01 之间所有分支的 merge 提交,显示提交简写哈希值%h、提交日期%ad、提交说明%s
git shortlog --author="lisi" --since="2023-11-01" --until="2023-12-01" --all --merges --format="%h %ad %s"

git reflog 深入追踪本地仓库的引用变化

git reflog命令记录了本地仓库的引用日志,包括分支更改和 HEAD 移动。这对于追踪仓库中引用的变化非常有帮助,即使在分支被删除或者历史被重写的情况下也能够追溯。

bash
# 查看本地仓库的引用日志
git reflog

输出类似于以下样例:

bash
a1b2c3d HEAD@{0}: commit: Your latest commit message
e4f5g6h HEAD@{1}: branch: Created from master
i7j8k9l HEAD@{2}: checkout: moving from master to feature-branch

每一行都提供了引用变化的详细信息,包括提交哈希、引用类型(如 HEAD、分支等)、变更操作(commit、branch、checkout 等)以及相应的提交信息。通过这个命令,你可以更深入地了解仓库中的引用变化历史。

git alias

bash
# 用基于文本的图形表示来记录提交历史。
lg = log --graph

# 每个项目占一行的日志记录。
lo = log --oneline

# 每个项目占一行的日志记录,按相反的顺序排列,即最近的项目先显示。
lor = log --oneline --reverse

# 生成修补程序的日志记录。
lp = log --patch

# 使用第一个父级的日志记录,对于只接受拉取请求的团队分支很有用。
lfp = log --first-parent

# 按拓扑顺序显示项目,即子提交在父提交之前显示。
lto = log --topo-order

# 显示我们首选选项的日志列表,即 log-list。
ll = log-list

# 显示长信息的我们首选选项的日志列表,即 log-list-long。
lll = log-list-long

# reflog - 参考日志,用于管理分支端点更新的时间。
rl = reflog

# From <https://gist.github.com/492227>
# 列出当前分支与origin/main分支之间的提交记录
heads = "!git log origin/main.. --format='%Cred%h%Creset;%C(yellow)%an%Creset;%H;%Cblue%f%Creset' | git name-rev --stdin --always --name-only | column -t -s';'"

# 显示已获取的新提交的日志,带有统计信息,排除合并
log-fresh = log ORIG_HEAD.. --stat --no-merges

# 显示我们偏好的信息的日志列表,即 `ll`
#
#   * 使用YYYY-MM-DD的短日期格式(无时间,无时区)
#   * 使用缩写十六进制的短哈希提交格式(非全十六进制)
#   * 使用名称的短作者字段(无电子邮件地址)和签名标记
#   * 没有列的短布局
#
log-list = log --graph --topo-order --date=short --abbrev-commit --decorate --all --boundary --pretty=format:'%Cblue%ad %C(auto)%h%Creset -%C(auto)%d%Creset %s %Cblue[%aN]%Creset %Cblue%G?%Creset'

# 显示我们偏好的信息的带有长格式的日志列表,即 `lll`
#
#   * 使用iso8601严格格式的长日期(YYYY-MM-DDTHH:MM:SS+HH:MM)
#   * 使用全十六进制作为名称-rev显示的长哈希提交格式
#   * 使用名称和电子邮件地址以及签名标记的长作者字段
#   * 使用列的长布局
#
log-list-long = log --graph --topo-order --date=iso8601-strict --no-abbrev-commit --decorate --all --boundary --pretty=format:'%Cblue%ad %C(auto)%h%Creset -%C(auto)%d%Creset %s %Cblue[%aN <%aE>]%Creset %Cblue%G?%Creset'

# 显示我自己提交的日志,使用我的用户电子邮件
log-my = "!git log --author \"$(git config user.email)\""

# 以图形方式显示日志
log-graph = log --graph --all --oneline --decorate

# 显示第一个提交(最早)的日期,以严格的ISO 8601格式
log-date-first = "!git log --date-order --format=%cI | tail -1"

# 显示最后一次提交(最新)的日期,以严格的ISO 8601格式
log-date-last = log -1 --date-order --format=%cI

# 显示最近一小时、一天、一周、一个月、一年的日志
log-1-hour  = log --since=1-hour-ago
log-1-day   = log --since=1-day-ago
log-1-week  = log --since=1-week-ago
log-1-month = log --since=1-month-ago
log-1-year  = log --since=1-year-ago

# 显示我自己最近一小时、一天、一周、一个月、一年的日志
log-my-hour  = "!git log --author \"$(git config user.email)\" --since=1-hour-ago"
log-my-day   = "!git log --author \"$(git config user.email)\" --since=1-day-ago"
log-my-week  = "!git log --author \"$(git config user.email)\" --since=1-week-ago"
log-my-month = "!git log --author \"$(git config user.email)\" --since=1-month-ago"
log-my-year  = "!git log --author \"$(git config user.email)\" --since=1-year-ago"

# 显示特定格式字符串及其日志条目数量
log-of-format-and-count = "!f() { format=\"$1\"; shift; git log \"$@\" --format=oneline --format=\"$format\" | awk '{a[$0]++}END{for(i in a){print i, a[i], int((a[i]/NR)*100) \"%\"}}' | sort; }; f"
log-of-count-and-format = "!f() { format=\"$1\"; shift; git log \"$@\" --format=oneline --format=\"$format\" | awk '{a[$0]++}END{for(i in a){print a[i], int((a[i]/NR)*100) \"%\", i}}' | sort -nr; }; f"

# 通过特定格式字符串和日期格式字符串显示日志条目数量
log-of-format-and-count-with-date = "!f() { format=\"$1\"; shift; date_format=\"$1\"; shift; git log \"$@\" --format=oneline --format=\"$format\" --date=format:\"$date_format\" | awk '{a[$0]++}END{for(i in a){print i, a[i], int((a[i]/NR)*100) \"%\"}}' | sort -r; }; f"
log-of-count-and-format-with-date = "!f() { format=\"$1\"; shift; date_format=\"$1\"; shift; git log \"$@\" --format=oneline --format=\"$format\" --date=format:\"$date_format\" | awk '{a[$0]++}END{for(i in a){print a[i], int((a[i]/NR)*100) \"%\", i}}' | sort -nr; }; f"

# 通过电子邮件显示日志条目数量
log-of-email-and-count         = "!f() { git log-of-format-and-count \"%aE\" \"$@\"; }; f"
log-of-count-and-email         = "!f() { git log-of-count-and-format \"%aE\" \"$@\"; }; f"

# 通过小时显示日志条目数量
log-of-hour-and-count          = "!f() { git log-of-format-and-count-with-date \"%ad\" \"%Y-%m-%dT%H\" \"$@\" ; }; f"
log-of-count-and-hour          = "!f() { git log-of-count-and-format-with-date \"%ad\" \"%Y-%m-%dT%H\" \"$@\" ; }; f"

# 通过日期显示日志条目数量
log-of-day-and-count           = "!f() { git log-of-format-and-count-with-date \"%ad\" \"%Y-%m-%d\" \"$@\" ; }; f"
log-of-count-and-day           = "!f() { git log-of-count-and-format-with-date \"%ad\" \"%Y-%m-%d\" \"$@\" ; }; f"

# 通过周显示日志条目数量
log-of-week-and-count          = "!f() { git log-of-format-and-count-with-date \"%ad\" \"%Y#%V\" \"$@\"; }; f"
log-of-count-and-week          = "!f() { git log-of-count-and-format-with-date \"%ad\" \"%Y#%V\" \"$@\"; }; f"

# 通过月份显示日志条目数量
log-of-month-and-count         = "!f() { git log-of-format-and-count-with-date \"%ad\" \"%Y-%m\" \"$@\" ; }; f"
log-of-count-and-month         = "!f() { git log-of-count-and-format-with-date \"%ad\" \"%Y-%m\" \"$@\" ; }; f"

# 通过年份显示日志条目数量
log-of-year-and-count          = "!f() { git log-of-format-and-count-with-date \"%ad\" \"%Y\" \"$@\" ; }; f"
log-of-count-and-year          = "!f() { git log-of-count-and-format-with-date \"%ad\" \"%Y\" \"$@\" ; }; f"

# 通过一天中的小时显示日志条目数量
log-of-hour-of-day-and-count   = "!f() { git log-of-format-and-count-with-date \"%ad\" \"%H\" \"$@\"; }; f"
log-of-count-and-hour-of-day   = "!f() { git log-of-count-and-format-with-date \"%ad\" \"%H\" \"$@\"; }; f"

# 通过一周中的天显示日志条目数量
log-of-day-of-week-and-count   = "!f() { git log-of-format-and-count-with-date \"%ad\" \"%u\" \"$@\"; }; f"
log-of-count-and-day-of-week   = "!f() { git log-of-count-and-format-with-date \"%ad\" \"%u\" \"$@\"; }; f"

# 通过一年中的周显示日志条目数量
log-of-week-of-year-and-count  = "!f() { git log-of-format-and-count-with-date \"%ad\" \"%V\" \"$@\"; }; f"
log-of-count-and-week-of-year  = "!f() { git log-of-count-and-format-with-date \"%ad\" \"%V\" \"$@\"; }; f"

# 显示简化的提交历史
log-refs = log --all --graph --decorate --oneline --simplify-by-decoration --no-merges

# 显示提交者、提交时间和提交信息
log-timeline = log --format='%h %an %ar - %s'

# 查看本地和远程分支的提交历史
log-local = log --oneline origin..HEAD

# 查看已拉取的提交历史
log-fetched = log --oneline HEAD..origin/main