这是Pro Git 2.3到2.7的阅读笔记,有点长,可以直接看小结。

查看历史修改

使用git log命令可以查看提交历史:

1
git log

你将看到类似如下输出:

1
2
3
4
5
commit f5c2e8b9d6e4e2a1b3c4d5e6f7a8b9c0d1e2f3g4
Author: Your Name <your.email@example.com>
Date: Mon Jan 1 12:34:56 2024 +0000

Initial commit

每个提交都有一个唯一的SHA-1哈希值、作者信息、日期和提交信息。你可以使用各种选项来定制git log的输出格式,例如:

  • git log --patch:显示每个提交的差异。实际上,你可以指定查看显示历史的数量,例如git log -p2只显示最近两个提交的差异。
  • git log --stat:显示每个提交的统计信息。
  • git log --pretty:自定义输出格式,常见的格式有oneline、short、full、fuller、reference等,如git log --pretty=oneline
  • git log --pretty=format:"%h - %an, %ar : %s":自定义输出格式,显示简短的哈希值、作者名、相对时间和提交信息。
    • %H :完整的哈希值
    • %h :简短的哈希值
    • %T :树对象的哈希值
    • %t :简短的树对象哈希值
    • %P :父提交的哈希值
    • %p :简短的父提交哈希值
    • %an :作者名字
    • %ae :作者邮箱
    • %ad :作者日期(可以通过--date=选项定制格式)
    • %ar :相对作者日期(如“2 weeks ago”)
    • %cn :提交者名字
    • %ce :提交者邮箱
    • %cd :提交者日期
    • %cr :相对提交者日期
    • %s :提交说明
      这里的--date=选项可以设置日期格式,如--date=relative--date=iso--date=rfc--date=short等。
      同时,提交者和作者的区别在于,提交者是实际执行提交操作的人,而作者是代码的原始编写者。
  • git log --grapgh:以图形化的方式显示分支和合并历史。
  • git log --name-only:只显示修改的文件名。
  • git log --since="2 weeks ago":只显示最近两周的提交。
  • git log --until="2024-01-01":只显示直到指定日期的提交。
  • git log --author="Your Name":只显示指定作者的提交。
  • git log --grep="fix":只显示提交信息中包含指定关键字的提交。
  • git log -S "function_name":只显示添加或删除了指定字符串的提交。

git log --author='Jonio C Hamano' --since'2008-10-01' --until='2008-10-31' --no-merges --pretty=format:'%h %ad | %s%d [%an]' -- t/: 显示2008年10月由Jonio C Hamano提交的非合并提交,格式化输出。其中--no-merges选项用于排除合并提交,-- t/表示只查看t/目录下的文件。

撤销操作

有时你可能忘记提交了一些更改,或者想要修改最近的提交信息。这时你可以使用git commit --amend命令来修改最近的提交:

1
git commit --amend

这将打开一个文本编辑器,允许你修改最近的提交信息。如果你想添加更多的更改到最近的提交中,可以先使用git add命令添加更改,然后再运行git commit --amend。最终新的提交将会替代原有的提交。
注意!使用git commit --amend会改变提交的哈希值,因此如果你已经将该提交推送到远程仓库,建议不要使用此命令,以免引起版本冲突。

假如你不小心把不需要提交的文件添加到了暂存区,我们之前介绍过使用git rm --cached <file>命令将其从暂存区移除,而使用git restore --staged <file>命令可实现相似的效果:

1
git restore --staged <file>

这将把指定的文件从暂存区移除,但保留工作目录中的更改。
要理解这两个命令的区别,先要理解:Git版本记录的是快照,每一个提交都拥有当前Git所跟踪的所有文件所构成的快照。这些快照一直都呆在暂存区(即cache,staging area)中。
git rm --cached命令实际上会直接从暂存区中移除该文件,此时提交快照将会缺失该文件,同时该文件将被标记为未跟踪状态,也就是说,使用了这个命令并提交之后,Git仓库中将不再出现这个文件,但是工作目录中仍然存在,且状态为未跟踪。
git restore --staged命令则不会将该文件从暂存区中移除,而是将它恢复至上一次提交的版本,同时保留当前工作目录中的版本,使用这个命令并提交之后,Git仓库中的原文件不会改变,而工作目录中则保持Modified的状态。

假如你想撤销对文件的修改,或者想要恢复成上次提交的样子,可使用git checkout -- <file>命令:

1
git checkout -- <file>

这将把工作区中的文件恢复到暂存区中的状态。这个操作是不可逆的危险命令。

从Git 2.23版本开始,推荐使用git restore <file>命令来实现同样的效果:

1
git restore <file>

这将把指定的文件恢复到上次提交的状态,丢弃所有未提交的更改。这个操作同样是不可逆的危险命令。

远程仓库

远程仓库(Remote Repository)是托管在网络服务器上的版本仓库。它拥有一套权限系统,允许多个用户协同工作,并且可以通过网络进行访问和操作。远程仓库通常托管在Git服务器上,如GitHub、GitLab、Bitbucket等。但是远程仓库实际上也可以存在于你的本地主机上,但是你依然需要通过标准的拉取、推送和抓取等操作来与之交互。

查看远程仓库

你可以使用git remote命令来查看已经配置的远程仓库。

1
2
3
4
5
git clone https://github.com/foo/bar.git
# 省略克隆过程
cd bar
git remote
origin

默认情况下,Git会将克隆的远程仓库命名为origin。你可以使用git remote -v命令来查看远程仓库的详细信息:

1
2
3
git remote -v
origin https://github.com/foo/bar.git (fetch)
origin https://github.com/foo/bar.git (push)

这显示了远程仓库的名称和URL,以及用于抓取和推送的URL。实际上,如果你与多个用户协同工作,可能会有多个远程仓库。比如这样

1
2
3
4
5
6
7
git remote -v
alice https://github.com/alice/bar.git (fetch)
alice https://github.com/alice/bar.git (push)
bob https://github.con/bob/bar.git (fetch)
bob https://github.con/bob/bar.git (push)
origin https://github.com/foo/bar.git (fetch)
origin https://github.com/foo/bar.git (push)

添加远程仓库

这些仓库并不都存在于你的本地机器上,而是以链接的方式存在于config文件中。你可以使用git remote add <name> <url>命令来添加一个新的远程仓库,这会向config文件中添加一条新的远程仓库配置:

1
2
3
4
5
6
7
8
9
10
git remote add charlie https://github.com/charlie/bar.git
git remote -v
alice https://github.com/alice/bar.git (fetch)
alice https://github.com/alice/bar.git (push)
bob https://github.con/bob/bar.git (fetch)
bob https://github.con/bob/bar.git (push)
origin https://github.com/foo/bar.git (fetch)
origin https://github.com/foo/bar.git (push)
charlie https://github.com/charlie/bar.git (fetch)
charlie https://github.com/charlie/bar.git (push)

拉取

如果你想要拉取所有alice的仓库中有的但是你没有的信息,可以使用git fetch <remote>命令:

1
2
3
4
5
6
7
git fetch alice
remote: Counting objects: 42, done.
remote: Compressing objects: 100% (30/30), done.
remote: Total 42 (delta 10), reused 35 (delta 5)
Unpacking objects: 100% (42/42), done.
From https://github.com/alice/bar
* [new branch] feature-x -> alice/feature-x

这里,“拉取”指的是从远程仓库获取最新的提交和分支信息,但不会自动合并到当前分支。具体来说,git fetch命令会下载远程仓库中的所有分支和标签的最新状态,并将它们存储在本地的远程跟踪分支(即本地对远程仓库某个分支的指向)中,例如alice/feature-x。这样,你可以在本地查看和操作这些远程分支,而不会影响当前的工作分支,也不会影响远程仓库的状态。当然,你也可以选择将这个分支合并到当前分支,或者创建一个新的本地分支来跟踪它。
如果你为当前分支设置了跟踪远程分支,那么你可以使用git pull <remote> <branch>命令来拉取并合并远程分支的更改:

1
git pull alice feature-x

这个命令会从alice的feature-x分支拉取最新的更改,并将其合并到当前分支。git pull实际上是git fetchgit merge的组合操作。默认情况下,git clone命令自动设置本地main分支来跟踪远程仓库的main分支,因此你可以直接使用git pull命令来更新本地main分支。

推送

要将本地的更改推送到远程仓库,可以使用git push <remote> <branch local>:<branch remote>命令:

1
git push alice main:feature-x

这个命令会将本地的main分支的更改推送到alice的feature-x分支。如果远程分支不存在,Git会自动创建它。你也可以使用简化的命令git push <remote> <branch>,这会将本地分支推送到同名的远程分支:

1
git push alice main

这个命令会将本地的main分支推送到alice的main分支。遇到多个推送产生冲突的情况时,Git会拒绝推送操作,并提示你先拉取远程分支的更改并解决冲突,然后再尝试推送。

查看远程仓库状态

如果想要查看远程仓库的状态,可以使用git remote show <remote>命令:

1
2
3
4
5
6
7
8
9
10
11
12
git remote show alice
* remote alice
Fetch URL: https://github.com/alice/bar.git
Push URL: https://github.com/alice/bar.git
HEAD branch: main
Remote branches:
feature-x tracked
main tracked
Local branch configured for 'git pull':
main merges with remote main
Local ref configured for 'git push':
main pushes to main (up to date)

它会显示远程仓库的URL、默认分支、跟踪的远程分支以及本地分支与远程分支的对应关系等信息。

重命名与移除

如果你想要重命名一个远程仓库,可以使用git remote rename <old-name> <new-name>命令:

1
2
3
4
5
6
git remote rename alice alice-updated
git remote
alice-updated
bob
origin
charlie

如果你想要移除一个远程仓库,可以使用git remote remove <name>命令:

1
2
3
4
5
git remote remove bob
git remote
alice-updated
origin
charlie

标签

标签(Tag)是用于标记特定提交的引用,通常用于标记发布版本。标签可以是轻量级的(lightweight)或附注的(annotated)。轻量级标签只是一个指向特定提交的指针,而附注标签则包含更多的信息,如标签创建者、日期和标签信息。

列出标签

你可以使用git tag命令来列出所有标签:

1
2
3
4
git tag
v1.0
v1.1
v2.0

也可以使用git tag -l "v1.*"命令来列出符合特定模式的标签:

1
2
3
git tag -l "v1.*"
v1.0
v1.1

创建标签

要创建一个轻量级标签,可以使用git tag <tagname>命令:

1
2
3
4
5
6
git tag v2.1
git tag
v1.0
v1.1
v2.0
v2.1

这个标签仅仅是一个指向当前提交的指针。
要创建一个附注标签,可以使用git tag -a <tagname> -m "message"命令:

1
2
3
4
5
6
7
git tag -a v2.2 -m "Version 2.2 release"
git tag
v1.0
v1.1
v2.0
v2.1
v2.2

这个标签则是存储在Git数据库中的一个完整对象,包含标签创建者、日期和标签信息,并可以用GPG进行签名以验证标签的真实性。可以使用git show <tagname>命令来查看标签的详细信息:

1
2
3
4
5
6
7
8
9
10
11
12
git show v2.2
tag v2.2
Tagger: Your Name <youremail@foo.com>
Date: Mon Jan 1 12:34:56 2024 +0000

Version 2.2 release

commit f5c2e8b9d6e4e2a1b3c4d5e6f7a8b9c0d1e2f3g4
Author: Someone Else <some@else.com>
Date: Sun Dec 31 11:22:33 2023 +0000

Some changes in this release

最后一行显示了标签所指向的提交信息。

后期创建标签

如果你想要为之前的某个提交创建标签,可以在git tag命令后面指定提交的哈希值:

1
2
3
4
5
6
7
8
git tag -a v1.5 f5c2e8b -m "Version 1.5 release"
git tag
v1.0
v1.1
v1.5
v2.0
v2.1
v2.2

共享标签

默认情况下,git push命令不会推送标签到远程仓库。要推送标签,可以使用git push <remote> <tagname>命令:

1
2
3
4
5
6
git push origin v2.2
Counting objects: 5, done.
Writing objects: 100% (3/3), 250 bytes | 250.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0)
To github.com:foo/bar.git
* [new tag] v2.2 -> v2.2

你也可以使用git push <remote> --tags命令来推送所有标签:

1
2
3
4
5
6
7
8
git push origin --tags
Counting objects: 15, done.
Writing objects: 100% (10/10), 1.25 KiB | 1.25 MiB/s, done.
Total 10 (delta 0), reused 0 (delta 0)
To github.com:foo/bar.git
* [new tag] v1.5 -> v1.5
* [new tag] v2.1 -> v2.1
* [new tag] v2.2 -> v2.2

这样,当其他人拉取远程仓库时,他们也会获得这些标签。

删除标签

如果你想要删除一个本地标签,可以使用git tag -d <tagname>命令:

1
2
git tag -d v1.5
Deleted tag 'v1.5' (was f5c2e8b)

如果你想要删除一个远程标签,可以使用git push <remote> :refs/tags/<tagname>命令:

1
2
3
git push origin :refs/tags/v1.5
To github.com:foo/bar.git
- [deleted] v1.5

或者使用这种简化的语法:

1
git push origin --delete tag v1.5

检出标签

如果你想要查看某个标签所指向的文件版本,可以使用git checkout <tagname>命令:

1
2
3
4
5
6
7
8
9
10
git checkout v2.0
Note: switching to 'v2.0'.

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 -b with the checkout command again. Example:
git checkout -b new-branch-name
HEAD is now at f5c2e8b... Some changes in this release

这会将工作目录切换到标签所指向的提交,但会进入“分离头指针(detached HEAD)”状态。在这种状态下,你可以查看文件内容,但不能直接在该状态下进行提交。如果你想要在标签的基础上进行开发,建议创建一个新的分支:

1
2
git checkout -b new-branch v2.0
Switched to a new branch 'new-branch'

如果在此之后又进行了一次提交,v2.0标签仍然指向原来的提交,而new-branch分支则指向新的提交。

Git别名

Git允许你为常用的命令创建别名,以简化操作。你可以使用git config --global alias.<alias-name> '<command>'命令来创建一个全局别名。例如:

1
2
3
4
5
git config --global alias.st 'status'
git config --global alias.co 'checkout'
git config --global alias.br 'branch'
git config --global alias.ci 'commit'
git config --global alias.lg "log --oneline --graph --all --decorate"

小结

在本节中,我们介绍了如何使用Git进行版本控制,包括查看提交历史、撤销操作、远程仓库的管理、标签的使用以及创建Git别名等。通过这些操作,你可以更高效地管理代码版本,协同工作,并保持代码的整洁和可追溯性。

下面是一个命令总结:

  • git log:查看提交历史。
  • git log --oneline:以简洁的方式查看提交历史。
  • git log --graph:以图形化方式查看提交历史。
  • git log --pretty=format:"%h - %an, %ar : %s":自定义提交历史的输出格式。
  • git log --patch:显示每个提交的差异。
  • git log --stat:显示每个提交的统计信息。
  • git log --name-only:只显示修改的文件名。
  • git log --since="2 weeks ago":只显示最近两周的提交。
  • git log --until="2024-01-01":只显示直到指定日期的提交。
  • git log --author="Your Name":只显示指定作者的提交。
  • git log --grep="fix":只显示提交信息中包含指定关键字的提交。
  • git log -S "function_name":只显示添加或删除了指定字符串的提交。
  • git commit --amend:修改最近的提交。
  • git restore --staged <file>:将文件从暂存区移除,但保留工作目录中的更改。
  • git checkout -- <file>:丢弃工作目录中的更改,恢复到上次提交的状态。
  • git remote:查看远程仓库。
  • git remote add <name> <url>:添加一个新的远程仓库。
  • git remote -v:查看远程仓库的详细信息。
  • git remote show <remote>:查看远程仓库的状态。
  • git remote rename <old-name> <new-name>:重命名远程仓库。
  • git remote remove <name>:移除远程仓库。
  • git fetch <remote>:从远程仓库获取最新的提交和分支信息。
  • git pull <remote> <branch>:拉取并合并远程分支的更改。
  • git push <remote> <branch local>:<branch remote>:将本地分支的更改推送到远程仓库。
  • git tag:列出所有标签。
  • git tag <tagname>:创建一个轻量级标签。
  • git tag -a <tagname> -m "message":创建一个附注标签。
  • git tag -l "v1.*":列出符合特定模式的标签。
  • git tag -d <tagname>:删除本地标签。
  • git checkout <tagname>:检出标签所指向的提交。
  • git push <remote> <tagname>:推送标签到远程仓库。
  • git push <remote> --tags:推送所有标签到远程仓库。
  • git config --global alias.<alias-name> '<command>':创建Git命令的别名。

附录:关于git的文件操作

git的文件操作相当之多,比较难以理解。但是实际上,它们都代表的是某种覆盖,也就是用一个版本的文件覆盖这个版本的文件。
Git中,文件的版本仅有三种:HEAD(最近一次提交),INDEX(暂存区),WORKTREE(工作区)。不同的操作实际上使用这三个版本中的一种覆盖另一种,或者直接删除其中一种。下面是根据这个思想制作的一张命令对照表(gpt generated)

命令 本质操作(树之间的关系) 实际语义
git add file INDEX ← WORKTREE 暂存修改
git commit HEAD ← INDEX 提交暂存区
git restore file WORKTREE ← INDEX 丢弃未暂存修改
git restore --staged file INDEX ← HEAD 取消暂存
git restore --staged --worktree file INDEX ← HEAD
WORKTREE ← HEAD
完全回滚文件
git checkout -- file WORKTREE ← INDEX 等价 restore
git reset HEAD file INDEX ← HEAD 取消暂存
git reset --hard HEAD INDEX ← HEAD
WORKTREE ← HEAD
核爆回滚
git rm file WORKTREE ← ∅
INDEX ← ∅
删除并暂存
git rm --cached file INDEX ← ∅ 只从版本控制删除
rm file(shell) WORKTREE ← ∅ 仅删工作区

2026年1月18日。