git版本控制工具(一)

所谓的版本控制工具其实就是帮助程序员进行代码状态的跟踪工具,方便程序员对代码的控制、维护等操作。

版本控制工具的好处:

  • 记录项目的修改历史,如若出现重大错误,方便回滚和维护。

  • 减少多人开发时的冲突,如若出现冲突,方便解决。

目前最流行的版本控制工具时git,git是一个分布式版本控制工具。

分布式版本控制

什么是分布式版本控制?

  • 客户端并不只获取最新的版本快照(即最新提交),而是将代码仓库完整的下载下来,包含完整的提交历史。

  • 这样,即使git服务器出现问题,但由于本地仓库的完整,仍然可以正常工作,只需要等待git服务器恢复正常,再次提交上去即可。

说起分布式,可能有些人会联想到集中式版本控制工具。

集中式版本控制工具确实存在,比如CVS(Concurrent Version System)和SVN(Subversion),但目前已经很少有人用了,这里就不赘述了。

集中式版本控制工具的优缺点

  • 优点:相较于最初的本地管理,集中式版本控制确实是一个令人非常好的方法,每个人一定程度上都能看到当前项目的状态,比如是否正在被人使用等。

  • 缺点:一旦中央服务器故障,只能等待服务器恢复才能继续提交,一旦故障时发生在磁盘,那么代码数据可能就会丢失。

Git使用

Git是一个分布式版本控制工具,既然是个工具,我们就需要先下载Git

Git配置文件所在位置一般是C:\Users\[用户名]\.gitconfig

Git安装时一般会安装Git BashGit CMD,它们其实都是命令行工具。

  • Git Bash:类似于Linux的命令行工具,可以执行Linux命令。同时基于CMD,又添加了一些新的命令和功能。

  • Git CMD:可以通过CMD命令行执行Git命令。

我们可以通过这项目命令行工具执行一些操作和修改git配置文件,我一般习惯用Git Bash,如:

1
2
3
4
5
6
7
8
9
10
11
12
# 设置用户名
git config --global user.name "your name"
# 获取用户名
git config [get] user.name
# 设置密码
git config --global user.password "your password"
# 获取密码
git config [get] user.password
# 设置邮箱
git config --global user.email "your email"
# 获取邮箱
git config [get] user.email

设置Git命令的别名

比如我们想查看文件的跟踪状态,需要执行git status,但我不想用status,那么我们可以给它设置一个别名,比如sta:

1
git config --global alias.sta status

这样就可以通过git sta命令查看文件的跟踪状态了。

取消git配置文件中的某一项:git config --global --unset [配置项键名]

获取Git仓库

如果我们用git管理项目,有两种方式:

  1. 初始化一个git仓库,在命令行使用git init命令,这个命令会在当前项目中创建一个.git隐式文件夹,而当前目录中除了.git文件夹外的文件会被标识为未被跟踪的状态.git文件夹与其他未被跟踪的文件共同构成了“git仓库”

  2. 从远程仓库中克隆一个git仓库,在命令行使用git clone [url]命令,这个命令会从远程仓库中克隆一个git仓库到本地,并把克隆的git仓库与远程仓库进行联系。

注意:git仓库的存在性是由.git文件夹决定的,无所谓当前目录中的其他文件是否被追踪。

即只要存在.git文件夹,当前目录就是一个git仓库,即使所有文件未被跟踪。

被Git管理的文件状态

我们可以通过判断文件的状态来识别文件是被被Git仓库管理:

  • 未跟踪(Untracked):未被Git仓库管理。

  • 已跟踪:被Git仓库管理。

上文提到,我们通过git init初始化一个git仓库时,仓库中的文件会被标识为未被跟踪的状态,如果我们创建了一个新的文件,新的文件也会被标识为未被跟踪的状态,这时我们可以通过git add将文件的状态转化为已跟踪状态,此时,Git系统可以对已跟踪状态状态下的文件进行各种状态管理。

已跟踪状态的文件又可以进一步进行状态划分:

  • Staged:暂缓区中的文件状态。

  • Unmodified:通过commit命令,将staged状态的文件提交到仓库后的文件状态。

  • Modified:修改了某个文件后,此文件会处于Modified状态。

文件的状态转换关系可以用下图概括:

查看文件的状态

前面我们提到了Git管理的文件有不同的状态,我们可以通过git status查看文件状态的转化,也就是所谓的跟踪文件状态。

对于新建的文件,Git不会对其进行跟踪,通过git status命令可以看出,它会直接进行未跟踪提示,如下图。

我们对使用git status -s简化查看内容,如下图:

我们可以看出:

  • ??:未跟踪状态。

  • M:已跟踪文件的修改状态。

  • A:文件已被跟踪的状态。

tip: 对于已加入暂存区但未提交的文件状态查看的标识(MA)是绿色的。

git add添加文件到暂存区

通过git add命令我们可以跟踪文件,实际上就是将文件提交本地Git仓库系统中的暂存区中。

git add命令的使用:

  • git add [文件名]:添加单个文件到暂存区,如:git add aaa.txt

  • git add [*.*]:通配符模式,如:git add *.txt

  • git add .:添加当前目录下的所有变更(包含新文件)。

  • git add -u/--upadte:将整个仓库下的变更进行提交(不包含新文件)

注意:对git仓库的操作,要在根目录进行,否者会出现部分没有被提交的情况。

git配置文件.gitignore

在项目开发中,我们总有一些文件是不希望被git管理的,比如node_modules.idea.vscode、日志文件等这些文件夹中的文件,这个时候我们就可以通过配置.gitignore文件来对它们进行忽略处理。

这个文件我们可以自己创建添加忽略项,如下:

1
2
3
4
5
6
7
8
9
# 整个文件夹
node_modules
.vscode

# 单个文件
bbb.txt

# 通配符模式
*.log

在GitHub中,有一个专门的[gitignore](https://github.com/github/gitignore)仓库,其中整理了一系列有用的.gitignore文件模板,我们可以根据自己开发的项目自行选择来使用。

我们也可以通过https://www.toptal.com/developers/gitignore这个网站填写关键词自动生成,比在上文模板中一个个找拼接方便多了。

比如我们想生成VueNode相关的.gitignore文件,如下图:

点击创建即可生成。

注意:.gitignore文件主要用于拦截未被跟踪(Untracked)的文件,对于已经提交到暂存的文件,在.gitignore中配置后还是会对其进行跟踪的。

要解决这个问题,我们只需要将文件变为未被跟踪的状态即可,本质上就是将文件从暂存区移除

实现的git操作为:

1
2
git rm --cached <file-path> # 单个文件
git rm -r --cached <directory> # 整个目录

这里就要提到git restore --staged操作了,此命令适于文件夹操作,用于将文件移出暂存区,就是撤销git add操作的。

1
2
3
4
5
# 将单个文件夹移出暂存区
git restore --staged path/to/folder/

# 将所有暂存文件移出暂存区
git restore --staged .

git commit文件更新提交

我们可以通过git commit命令将暂存区中的文件新增和修改进行提交,我们可以通过-m配置为本次提交添加备注。

如下:

1
git commit -m '提交信息'

我们在进行提交操作前,都需要通过git add将当前git仓库中的文件的状态进行更新,才能将当前版本的代码进行提交。

我们可以使用git commit -a -m '提交信息'简化上述操作,这个命令其实就是git add -ugit commit -m '提交信息'的结合。

上文我们提到了git add -u的作用,所以我们知道:git commit -a -m '提交信息'此命令只适用与已存在文件的提交

Conventional Commits提交规范

对于以前的git提交信息,有些只是一行字符串,在多人项目开发中,一旦有人的提交注释含糊不清,就会导致阅读性很差。

目前有一套主流提交规范来解决这个问题:Conventional Commits(约定式提交)

在此规范中,提交信息由Header、Body和Footer三部分组成,格式如下:

1
2
3
4
5
<type>(<scope>): <subject>
// 空行
<body>
// 空行
<footer>

这里给出几个优秀的提交示例:

1
2
3
4
5
6
fix(login): 修复密码验证逻辑错误

当密码包含特殊字符时正则匹配失败,
添加对 @#$% 符号的支持

Closes #112
1
2
3
4
5
6
7
feat(payment): 集成支付宝支付

- 添加 Alipay SDK 依赖
- 实现支付结果回调接口
- 更新订单状态机逻辑

BREAKING CHANGE: 移除旧的支付接口 /api/v1/pay

Conventional Commits提交规范——Header部分(必需)

  • type(提交类型)

类型 说明 示例
feat 新增功能 feat:添加登录功能
fix 修复bug fix: 解决空指针异常
docs 文档更新 docs: 更新 API 文档
style 代码格式调整(不影响运行) style: 格式化代码
refactor 重构代码(非功能修改) refactor: 优化缓存逻辑
perf 性能相关 perf: 减少渲染耗时
test 测试相关 test: 增加单元测试
chore 构建/依赖更新 chore: 升级 webpack
revert 回滚提交 revert: 撤销某次提交
  • <scope>(可选):说明影响范围。

    • feat(ui): 添加按钮组件
    • fix(auth): 修复登录失效问题
  • <subject>(描述):简洁的祈使句(现在时)。

    • ✅正确:add feature X
    • ❌错误:added feature X 或 adding feature X

Conventional Commits提交规范——Body部分(可选)

Body格式:

  • 解释提交目的和实现逻辑

  • 每行不超过72个字符

  • 使用祈使句

如:

1
2
3
4
5
6
7
为什么需要此修改:
- 旧逻辑无法处理并发请求
- 导致用户数据丢失

如何解决:
- 引入分布式锁机制
- 增加重试机制

Conventional Commits提交规范——Footer部分(可选)

Footer格式:

  • 关联Issue:关闭问题过任务,如Closes #123Fixes ABCD-123

  • 破坏性更改(BREAKING CHANGE),如BREAKING CHANGE: 移除旧版 API

使用Conventional Commits规范格式提交

通过命令行进行提交

  1. 通过git add .将文件加入暂存区。

  2. 在命令行输入git commit(等价于git commit -e),回车后进行编辑器(通常是Vim),在空白处按规范格式填写即可。

使用工具(Commitizen)进行提交

上文提到,我们可以通过Vim编辑器进行自己编写,但手写的情况下过于麻烦,也容易写错单词,这时我们就可以使用Commitizen工具快速的生成一个标准提交信息进行提交。

(1) 安装Commitizen(交互式提交工具)

1
npm i -g commitizen

(2) 初始化适配器

1
commitizen init cz-conventional-changelog --save-dev --save-exact

(3) 使用commitizen命令提交(代替git commit

1
2
git add .
npx cz/git-cz # commitizen全局安装后直接使用 cz/git-cz即可

回车后就会出现一个交互页面,按需操作即可:

注意:在输入body内容,即Provide a longer description of the change: (press enter to skip)这一步,可能会出现只能输入一行内容,回车后直接进入footer输入阶段。

解决方法:

  • 方法一:使用[npx] cz/git-cz -e命令,加入-e配置后,会在生成符合约定格式的信息后,再打开编辑器让我们编辑,这个时候我们就可以对body进行多行设置了。

  • 方法二:修改cz-conventional-changelog库的源文件内容

    • 修改过后在输入body时会弹出一个编辑器页面进行单独填写。

    • 但这样有一个弊端,我们修改的内容是本地修改的,而在git远程同步时,是不会同步node_modules中的内容的,如果别人想要使用是需要再次单独修改的。

  • 方法三:通过\n操作符实现换行操作

强制校验提交规范

上文我们提到了git提交时的约定式提交规范,但在多人项目中,每个人是否遵守这个规范都是不确定的,如果我们想规定项目成员都遵守这套规范,我们就可以使用commitlint工具。

(1) 安装校验工具

1
npm install -D @commitlint/cli @commitlint/config-conventional

(2) 创建commitlint.config.js配置文件,并继承预设规则

1
2
3
echo "module.exports = { extends: ['@commitlint/config-conventional'] };" > commitlint.config.js
# 或
echo "export default { extends: ['@commitlint/config-conventional'] };" > commitlint.config.js

也可自行创建commitlint.config.js文件,自定义配置文件内容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// 此处使用commonjs模块语法,也可使用ESModule语法,后续可能ESlint校验可能需要使用ESModule语法
module.exports = {
// 继承预设规则 (推荐使用 @commitlint/config-conventional)
extends: ['@commitlint/config-conventional'],
// 可选:自定义规则 (覆盖或扩展预设规则)
rules: {
// 例如:允许 'chore' 类型
'type-enum': [
2,
'always',
[
'build',
'chore',
'ci',
'docs',
'feat',
'fix',
'perf',
'refactor',
'revert',
'style',
'test',
],
],
// 例如:主题行首字母可以大写
'subject-case': [
2,
'always',
['sentence-case', 'start-case', 'pascal-case', 'upper-case'],
],
// ... 其他自定义规则
},
};

(3) 添加钩子

我们可以使用Husky这个git hooks管理器。

Husky 是一个用于管理 Git 钩子的工具,它允许开发者在 Git 操作的特定阶段(如提交前、提交后、推送前等)执行自定义脚本。这些脚本通常用于自动化任务,比如代码格式化、测试运行、提交消息验证等。

常见Git钩子及Husky应用场景:

钩子类型 触发时机 典型应用场景 Husky 脚本位置
pre-commit 提交消息输入前 代码格式化 (Prettier)、Lint 检查 .husky/pre-commit
commit-msg 用户输入提交消息后 提交消息验证 (commitlint) .husky/commit-msg
pre-push 推送到远程仓库前 运行完整测试套件 .husky/pre-push
post-merge 合并操作完成后 自动安装新依赖 (npm install) .husky/post-merge
pre-rebase 变基操作开始前 检查分支状态 .husky/pre-rebase
post-checkout 切换分支后 环境配置更新 .husky/post-checkout

注意:commitlint目前不支持pre-commit钩子

安装husky

1
npm install --save-dev husky

初始化husky配置

1
npx husky init

上述初始化操作会创建一个.husky文件夹并初始化一些配置,同时会自动帮我们创建pre-commit脚本,并更新package.json中的prepare脚本,但前面我们已经提到过:commitlint目前不支持pre-commit钩子,所以这个pre-commit脚本我们需要删除。

然后我们需要创建commit-msg脚本。

1
2
3
4
5
6
# Add commit message linting to commit-msg hook
# 在 commit-msg 钩子中添加提交消息 linting
echo "npx --no -- commitlint --edit \$1" > .husky/commit-msg
# Windows users should use ` to escape dollar signs
# Windows 用户应使用 ` 转义美元符号
echo "npx --no commitlint --edit `$1" > .husky/commit-msg

我们也可以自己在.husky文件夹下创建commit-msg脚本,并填写以下内容:

1
npx --no -- commitlint --edit $1

然后对我们的提交就会进行校验操作了。

lint-staged:git提交前对代码质量进行检查

lint-staged是一个针对 Git 暂存区(staged files)运行代码检查工具(linters)的 Node.js 工具。它的核心功能是仅对即将提交的文件运行指定的代码检查和格式化操作,而不是对整个项目进行检查。

为什么要使用lint-staged?

在传统项目开发中,是对整个项目进行Lint操作,如果项目内容少的情况下,还是可以的,但内容多起来,仅仅是Lint操作,就是一个非常耗时的操作,非常影响开发,而lint-staged的解决方案是处理Git暂存区的文件,检查时间大大降低,并且它是在git提交前进行操作的,与Husky、ESLint、Prettier、Commitlint工具结合既能进行代码自动修复,又有质量保证,还能使团队开发统一,强制所有开发者使用相同的代码标准。

安装lint-staged

1
npm install --save-dev lint-staged

前面我们提到了lint-staged能与Husky、ESLint、Prettier、Commitlint结合,前面已经安装过了Husky和commitlint,我们把剩下的也安装一下

1
2
3
4
5
# 安装代码质量工具
# 安装 eslint
npm init @eslint/config@latest
# 安装 prettier
npm install --save-dev --save-exact prettier

上述eslint在安装时可以自己选择配置,并且会生成对应的配置文件,我们就不用单独创建了。

创建husky的pre-commit脚本,并填写以下内容:

1
echo "npx lint-staged" > .husky/pre-commit

创建perttier配置文件.prettierrc.mjs,并填写以下内容,也可以自定义配置

1
2
3
4
5
6
7
8
const config = {
trailingComma: 'es5',
tabWidth: 4,
semi: true,
singleQuote: true,
};

export default config;

最后填写lint-staged配置,有两种配置方式,如下图

我们简单填写一下即可

1
2
3
4
{
"*.{js,jsx,ts,tsx}": ["prettier --write", "eslint --fix --max-warnings=0"],
"*.{json,md,yml,css,scss}": ["prettier --write"]
}

这一套下来就可以测试看看是否生效了。

前端代码提交校验(Husky、ESLint、Prettier、Commitlint、lint-staged、Commitlizen)工作流图示

git查看提交的历史

我们想要查看一下我们的提交历史,可以使用git log命令,默认会按时间顺序列出提交信息,包含每个提交的SHA-1校验和、作者名、电子邮件、提交时间和提交说明。

我们也可以通过配置简单查看提交记录

  • 通过--oneline/--pretty=oneline一行查看,前者会展示校验和的前几位。

  • 再输入--graph配置可以以图的形式查看

git版本回退

我们如果想回退到之前提交的某个版本,可以使用git reset命令,其实就是改变HEAD的指向(Git通过HEAD指针记录当前版本,HEAD指针总是指向当前分支的最后一次提交)。

所以,我们只需改变HEAD指向就能进行版本回退

  • 我们可以通过^进行版本回退,如git reset --hard HEAD^,一个^表示回退上一个版本,如果是^^表示回退上上个版本。

  • 使用~,比如我们想回退到上50个版本,可以使用git reset --hard HEAD~50

  • 我们可以通过某个版本的校验和实现特定版本回退,如git reset --hard 5b521

对于指定校验和回退,其实没必要输入完整的校验和,只需输入前几位,git会自动帮我们寻找。上述操作都需要加上--hard配置项,否者会造成提交记录回退,但仓库内容还是当前版本,只不过回退版本之后的更改都变为Untracked未跟踪状态。