Skip to content

9.2 Git 基础

Git 基础部分包括命令行的使用以及常用的操作实践,这部分内容是我们真正在写代码时会用到的技术。如果对前面介绍的原理部分理解足够的话,这部分的实践也会轻松许多。

9.2.1 Git 基本配置

安装 Git 之后,要还要对当前的 Git 环境做一个基础配置。你要做的第一件事是设置你的用户名和邮箱,因为在创建版本时,用户名和邮箱会被写入到版本中,这样在后期查看版本时才能看到当前版本的提交者。

在一台电脑中,Git 配置一般分为三个级别,分别是系统级配置、用户级配置、项目级配置。不同级别的配置对应不同的配置文件,具体如下:

  • /etc/gitconfig:系统级配置文件,对所有用户生效。
  • ~/.gitconfig:用户级配置文件,对当前用户下的所有仓库生效。
  • .git/config:项目级配置文件,只对当前仓库生效。

Git 提供了一个 “git config” 命令来操作配置文件和获取配置信息。设置用户名和邮箱时,如果当前目录下存在 Git 仓库,那么默认会设置为项目级配置。一般建议用户名和邮箱设置为用户级配置,方式如下:

sh
$ git config --global user.name "ruidoc"
$ git config --global user.email ruidocgo@gmail.com

上述命令使用 --global 参数在用户级 Git 配置中设置了用户名和邮箱。其他级别的配置设置方式如下:

sh
$ git config --local user.name "xxx" # 设置项目级配置
$ git config --system user.name "xxx" # 设置系统级配置

设置之后,可以用以下方式查看配置信息:

js
$ git config --list

该命令会列出所有级别的 Git 配置。如果想查看指定级别、或者直接查看配置项的来源,使用以下方法:

sh
$ git config --global --list # 查看用户级配置
$ git config --list --show-origin # 查看所有配置,并显示配置来源

9.2.2 文件跟踪与暂存区

还是以 git_demo 文件夹(下文称项目目录)为例,在创建 Git 仓库之后,项目目录下的文件和 Git 仓库还没有建立起关联。结合前面介绍的 Git 原理可知,此时的文件属于已修改未跟踪状态。

如何建立关联并跟踪文件呢?方式很简单,只需要将文件添加到暂存区,这样项目目录下的文件就会被纳入 Git 版本控制中,此时即便文件丢失也可以找回来。

添加暂存区使用“git add”命令,将所有文件添加到暂存区命令如下:

sh
$ git add .

“git add” 命令后的参数是要暂存的文件名,符号“.”代表所有文件。假设在项目目录中添加一个 a.js 文件并加入暂存区,此时通过 “git status 命令查看仓库状态。

sh
$ git status
On branch test
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
	new file:   a.js

可以看到 a.js 文件已被跟踪,并被标识为 new file。

我们说加入暂存区后即便文件丢失也能找回来,现在可以尝试一下,将 a.js 文件删除,再查看仓库状态:

sh
$ git status
On branch test
Changes not staged for commit:
  (use "git add/rm <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
	deleted:    a.js

查看项目目录会发现 a.js 文件已经被删除。但是别慌,我们在暂存区还有文件备份,因此有办法恢复。恢复方式也很简单,就是丢弃工作区对 a.js 文件的删除,命令如下:

sh
$ git restore a.js

此时 a.js 会恢复到工作区中。请注意:虽然暂存区的文件可以恢复,但毕竟是“暂存”,如果想要可靠的恢复,文件必须提交,这样文件的修改也会与其他人分享。

9.2.3 创建和查看提交

添加到暂存区是一个过渡阶段,最终目的还是要创建一个新的版本,在 Git 中创建版本被称为“提交”。提交一条记录,就是创建一个版本,通过 “git commit” 命令来实现。

提交记录只会把暂存区的内容提交为一个新版本,因此提交时需要“暂存+提交”联合使用。现在我们来创建项目中的第一个提交:

sh
$ git add .
$ git commit -m '初始化提交'

上面命令中的 -m 参数表示提交描述,后面跟一个字符串,描述本次提交做了什么事情。提交描述很关键,在团队协作中需要严格遵守规范,不可随意填写,否则会让提交记录显得混乱不堪。

第一次提交会将项目中的所有代码备份,所以跟踪的文件数量会比较大。提交后我们可以通过 “git log” 查看提交记录,可以看到我们创建的第一个提交,如图 10-3 所示。

我们可以继续创建一个新文件 b.js,然后再提交一次:

sh
$ git add b.js
$ git commit -m '创建b.js'

接着再执行 “git log” 命令,会看到两条记录都显示了出来,最新的记录排在最前面。

提交记录中展示了作者和提交时间,最关键的是 commit 属性后面的长长的字符串,它是本次提交的唯一标识,被称为 commitId。在涉及到对某次提交的操作时,我们会频繁用到这个值。

比如,我要查看某次提交详细的文件变化,使用以下命令:

sh
$ git show [commitId]
# 查看第一条提交
$ git show 62c655f

注意:由于 commitId 比较长,在使用时略有不便,因此通常做法是只取 commitId 的前 7 位,如上述命令中的使用方式,同样可以保证 commitId 的唯一性。

9.2.4 撤销与回滚

开发过程中频繁使用 git 拉取推送代码,难免会有误操作。这个时候不要慌,git 支持绝大多数场景的撤回方案,我们来总结一下。

撤回主要依靠是两个命令实现:reset 和 revert。

git reset

reset 命令的原理是根据 commitId 来恢复版本。因为每次提交都会生成一个 commitId,所以说 reset 可以帮你恢复到历史的任何一个版本。

reset 命令格式如下:

sh
$ git reset [option] [commitId]

比如,要撤回到某一次提交,命令是这样:

sh
$ git reset --hard cc7b5be

上面的命令,commitId 是如何获取的?很简单,用 “git log” 命令查看提交记录,就可以看到 commitId 值,我们取前 7 位即可。

这里 option 的值为 --hard,表示强制撤销。option 选项共有 3 个值,具体含义如下:

  • --hard:撤销 commit,撤销 add,删除工作区改动代码
  • --mixed:默认参数。撤销 commit,撤销 add,还原工作区改动代码
  • --soft:撤销 commit,不撤销 add,还原工作区改动代码

这里要格外注意 --hard,使用这个参数恢复会删除工作区代码。也就是说,如果你的项目中有未提交的代码,使用该参数会直接删除掉,不可恢复,因此使用时要慎重!

除了使用 commitId 恢复,“git reset” 还提供了恢复到上一次提交的快捷方式:

sh
$ git reset --soft HEAD^

HEAD^ 表示上一个提交,它是一个指向上一个提交的指针。该值可以多次使用。

其实平日开发中最多的误操作是这样:刚刚提交完,突然发现了问题,比如提交信息没写好,或者代码更改有遗漏,这时需要撤回到上次提交,修改代码,然后重新提交。

这个流程大致是这样的:

sh
# 1. 回退到上次提交
$ git reset HEAD^
# 2. 修改代码...
...
# 3. 加入暂存
$ git add .
# 4. 重新提交
$ git commit -m 'fix: ***'

针对这个流程,git 还提供了一个更便捷的方法:

sh
$ git commit --amend

这个命令会直接修改当前的提交信息。如果代码有更改,先执行 “git add”,然后再执行这个命令,比上述的流程更快捷更方便。

reset 还有一个非常重要的特性,那就是“真正的后退版本”。

什么意思呢?就是说 reset 相当于直接删除了某个版本,然后用新的提交替代,这样回退的版本就不复存在了,也无法再找回。这个操作需要在非常明确没有问题的情况下使用,否则会带来丢失代码的风险。

更安全的方案是什么?它就是下面要介绍的第二个命令:“git revert”。

git revert

revert 与 reset 的作用一样,都是恢复版本,但是它们两的实现方式不同。

简单来说,reset 直接恢复到上一个提交,工作区代码自然也是上一个提交的代码;而 revert 是新增一个提交,但是这个提交是使用上一个提交的代码。

因此,它们两恢复后的代码是一致的,区别是一个新增提交(revert),一个回退提交(reset)。

正因为 revert 永远是在新增提交,因此不会有删除历史提交的风险,这样误操作时找回代码也很方便,提高了安全性。

说完了原理,我们再看一下使用方法:

sh
$ git revert -n [commitId]

掌握了原理使用就很简单,只要一个 commitId 就可以了。

9.2.5 合并提交

对于修改过的代码,Git 允许我们随时随地提交版本,即便只是修改了一个字母。然而从长远的角度来看,频繁的提交内容,特别是修改很小的内容,会造成提交过于冗余,且不符合最佳实践。

一般情况下,完成一个功能或修复一次 bug 会创建一个提交,这样会使提交更简洁清晰。但有时候可能昨天完成了一部分,先提交一次,今天完成剩余的部分,这个功能就被拆分为了两个提交。

这个时候我们可以把两个/多个提交合并为一个提交,怎么做呢?先看最简单的办法。

  1. 回退+重新提交

合并提交一般是将最新的一个或多个提交合并。我们可以换一种思维,将代码回退到某个历史提交,并将这个提交之后的代码变更重新提交,这样就间接完成了合并提交的操作。

假设我们要合并前 3 个提交,就需要将代码恢复到第 4 个版本,这样才能将前 3 个提交的文件变更重新提交。假设第 4 个提交的 commitId 是 “5c6snc8”,恢复方式如下:

sh
# 回退版本,并将变更放入暂存区
$ git reset --soft 5c6snc8
$ git add .
$ git commit -m '合并后的提交'

经过这样三个步骤,我们就完成了合并提交 ——— 用一个新的提交替代原有的三个提交。

  1. git rebase -i

回退的方式固然能帮助我们理解合并提交的本质,但是操作起来略显繁琐,我们再认识一个更简单好用方式 ——— “git rebase -i”。

rebase 命令的含义是变基,现在你不需要了解变基是什么,只需要知道合并前 3 个提交的话,直接执行以下命令:

sh
$ git rebase -i HEAD~3

命令中的 “HEAD~3” 表示合并 3 个提交,合并 4 个提交写成 “HEAD~4” 即可。执行之后,终端会进入编辑模式,你可以看到编辑区域有以下三行关键的代码:

pick 10fb9f2 创建b.js
pick 2f504f4 创建c.js
pick 1297045 创建d.js

很显然这是三个提交,我们要编辑它们告诉 Git 如何处理这些提交。使用 git rebase 和使用 git reset 合并提交的思路不同,不需要关心第 4 个提交,而是直接将前两个提交并入到第 3 个提交上。方法是将前两个提交由 “pick” 改为 “s”,修改如下:

pick 10fb9f2 创建b.js
s 2f504f4 创建c.js
s 1297045 创建d.js

修改后保存并退出编辑模式,此时会进入另一个编辑区域,这里编辑新的提交描述。现在保存并退出,在终端执行 “git log” 查看记录,既可以看到之前的 3 个提交合并为 1 个提交了。

9.2.6 标签与别名

Git 非常强大,与此同时它的一些操作也非常复杂。对于一些频繁使用的功能,Git 提供了快捷方式来帮助我们更简单便捷地使用 Git,这些快捷方式的代表就是标签和别名。

标签

一个大型项目中往往会有数量庞大的提交,对于一些有着特殊意义的关键提交(比如版本更新),我们需要一种更直观的方式对提交进行标记,这就是标签的由来。

标签使用 tag 表示,操作标签使用 “git tag” 命令来实现。

请注意:标签只是某个 commitId 的特殊标记,因此创建一个标签,就相当于是为某个提交添加了一个快捷方式,之后可以通过标签定位到这个提交。

  1. 创建标签

比如我要创建一个 v1.0.0 的标签表示版本号,方法如下:

sh
$ git tag -a v1.0.0 -m "新发布版本"

上面命令中的 -a 参数表示附注标签,意思是可以添加注释的标签,并且用 -m 参数指定注释内容。

Git 标签分为“轻量标签”和“附注标签”两类。轻量标签没有选项参数,也不支持描述,因此大多时候我们需要的是附注标签。本节介绍的标签代指附注标签。

默认情况下,新创建的标签与最新的提交关联,因此标签 v1.0.0 指向当前最新的提交。

那么能否为历史的某个提交打标签呢?当然是可以的,只需要在上述命令中指定 commitId 即可。假设某个历史提交的 commitId 为 62c655f,打标签方式如下:

sh
$ git tag -a v0.0.1 62c655f -m '历史提交'

通过这两种创建标签的方式,我们可以为任何一个提交打标签。

  1. 查找标签

创建标签之后,还需要查看标签。首先是使用 “git tag” 命令查看标签列表:

sh
$ git tag
v0.0.1
v1.0.0

可以看到,我们前面创建的两个标签都列出来了。如果要查看标签的详细内容,使用 “git show” 命令:

sh
$ git show v1.0.0

> tag v1.0.0
Tagger: ruidoc <ruidocgo@gmail.com>
Date:   Tue Apr 4 07:58:17 2023 +0800

新发布版本

commit d595fbeea5adcdd022726914359d674d2235bf00

查看标签详情可以看到标签的作者和描述信息,以及标签对应的提交信息。我们说标签是提交的快捷方式,那么一些提交的操作也可以通过标签来实现。比如回退版本:

sh
$ git reset v0.0.1

通过这种方式,将版本切换到 v0.0.1 对应的那一次提交。

  1. 删除标签

删除标签使用 -d 参数来实现,如删除 v0.0.1,命令如下:

sh
$ git tag -d v0.0.1
Deleted tag 'v0.0.1' (was 1e1a25e)

接着使用 “git tag” 命令查看标签列表,发现 v0.0.1 已经没有了。

别名

如果说标签是提交的快捷方式,那么别名就是命令的快捷方式。对于一些频繁使用的 Git 命令,如果能让它变得更加简短,无疑会大大提升我们的工作效率。

Git 别名属于配置项,因此也通过 “git config” 来设置。假设我要为提交命令 “git commit” 设置一个别名,方法如下:

sh
$ git config --global alias.ci "commit -m"

上面命令中设置了别名 “ci” 来替代 Git 子命令 “commit -m”。在创建新提交时,以下两种写法效果一致:

sh
$ git commit -m "新提交"
$ git ci "新提交"

很显然,使用别名后的命令更简短快捷。通常情况下我们只对 Git 子命令做别名,当然 Git 也支持对系统命令设置别名,方法是在命令前面加入 “!” 符号。

我们设置一个别名 “nd” 查看当前 Node.js 的版本,如下:

sh
$ git config --global alias.nd "! node -v"

设置后以下两个命令的运行结果一致:

sh
$ git nd
$ node -v

根据这个思路,我们就可以把 Git 中使用最频繁的命令 ——— 加入暂存区并提交合成一个简单的别名,如下:

sh
$ git config --global alias.ct "! git add . && git commit -m"

以后在创建提交时,直接使用 “git ct <提交描述>” 就可以啦。