一、背景
Git
作为一项非常重要的研发软技能,需要我们在能熟练处理基本场景之外,面对复杂的分支情况还能找到最优解。
最近在公司将自己的分支合并入版本大分支时,遇到了很多问题,可谓是被 Git
暴捶了一番,于是下定决心,狠狠的整理一波,同时将遇到的一些复杂情景做一个详细的复盘!
首先说一下通过这篇文章大家能收获什么:
- 巨详细的
Git
常用指令。- 工作中需要学会的
Git
进阶指令。- 没见过但需要了解的
Git
指令
为防止文章太长过于臃肿,打算在下一章再结合实际工作中的复杂情况介绍指令的运用方式(还在努力更新中)
二、Git 常用操作
这里我们默认大家都有一定的基础情况下 ,底子薄弱的可以查阅相关的文章沉淀一下(大大滴有
这边再贴一个 Git
练习网站:
learngitbranching.js.org/?locale=zh_... (很直观,但内容稍微单薄,对于入门还是很有帮助的!)
2.1 Git 基本概念
Git
是一个强大的版本控制系统,用于跟踪和管理文件的变化。它广泛应用于软件开发,但也适用于任何需要文件版本管理的场景。接下来让我们逐步深入了解 Git
的基本概念和组成部分。
基本概念
- 版本控制系统(VCS) :
Git
是一个分布式版本控制系统,它允许多个用户在不同的系统上工作,同时跟踪和管理文件的变化。 - 仓库(Repository) :
Git
仓库是存储项目文件及其历史记录的地方。一个仓库包括了所有的提交(commit)和分支(branch)。
关键区域和流程
-
工作区(Working Directory) :
- 这是用户对文件进行修改的地方。
- 工作区包含了当前项目的所有文件和目录。
-
暂存区(Staging Area) :
- 暂存区是一个准备好下一次提交的改动的区域。
- 用户可以选择性地将工作区的修改添加(add)到暂存区。
-
本地仓库(Local Repository) :
- 本地仓库是存储在用户计算机上的仓库。
- 用户可以将暂存区的内容提交(commit)到本地仓库,这样就会在仓库历史中创建一个新的"快照"。
-
远程仓库(Remote Repository) :
- 远程仓库通常位于服务器上,例如
GitHub、GitLab、Gitee
。 - 用户可以将本地仓库的更改推送(push)到远程仓库,或从远程仓库拉取(pull)最新更改。
- 远程仓库通常位于服务器上,例如
HEAD
HEAD
是一个指针,它指向当前所在分支的最新提交,当然 HEAD
是可以移动的:
-
使用提交哈希值 :通过指定提交的哈希值,可以直接将
HEAD
移动到该提交。例如,使用命令git checkout <commit>
可以将HEAD
移动到指定的提交上。 -
使用相对引用:常用的相对引用包括:
HEAD^
:将HEAD
移动到当前提交的父提交。HEAD~n
:将HEAD
移动到当前提交的第 n 个父提交。
-
使用分支或标签 :通过切换到特定的分支或标签,也可以将
HEAD
移动到该分支或标签所指向的提交。使用命令git checkout <branch>
或git checkout <tag>
可以实现这一点。
在了解好这些知识点后,我们进一步介绍一下与区域相关的操作
2.2 Git 工作区常用操作
提交代码至暂存区
-
基本用法 :
git add <文件名>
或git add <目录>
。这个命令将工作区的文件或目录的更改添加到暂存区。 -
选项:
-A
或--all
:添加所有更改(包括新文件和删除的文件)到暂存区。-u
:只添加被修改和删除的文件,不添加新文件。
储藏更改
git stash
命令会将当前的工作区状态保存在Git存储库内部的一个特殊的地方,称为"储藏堆栈"(stash stack)。它不会直接存储在工作区、暂存区或本地仓库中。
- 储藏当前工作区的修改 :
git stash save
或git stash
将会保存当前工作目录和暂存区的修改,并将其储藏起来。我们可以给储藏项添加一个可选的描述信息,例如git stash save "WIP: Work in progress"
。 - 查看储藏列表 :
git stash list
可以显示当前存在的所有储藏项。每个储藏项都有一个唯一的标识符(stash@{n})和描述信息。 - 应用储藏的修改 :
git stash apply
用于将最新的储藏项应用到当前工作目录。这样会将储藏的修改应用到工作区和暂存区,但不会从堆栈中删除该储藏项。 - 应用并删除储藏的修改 :
git stash pop
与git stash apply
类似,但它将应用最新的储藏项并从堆栈中删除它。 - 应用指定的储藏项 :
git stash apply stash@{n}
可以选择性地应用堆栈中的特定储藏项。 - 删除储藏的修改 :
git stash drop stash@{n}
可以删除堆栈中的特定储藏项,使其不再可用。 - 删除所有储藏项 :
git stash clear
可以删除储藏堆栈中的所有储藏项。 - 创建新分支并应用储藏的修改 :
git stash branch <branch_name>
将会创建一个新的分支,并将最新的储藏项应用于该分支。
撤销更改
虽然相关的编译器提供的 Git
插件已经提供了非常便利的撤销操作,但我们还是要熟练掌握相关的撤销命令。
-
丢弃工作区的更改:
git checkout -- <filename>
:这个命令将丢弃工作区对指定文件的更改,恢复成最近一次提交的状态。这意味着我们将丢失在上次提交之后对该文件所做的所有修改。请注意,如果没有添加文件名,那么它将会丢弃工作区中所有文件的修改。git restore <filename>
:这个命令在较新的Git
版本中用来撤销工作区的更改。它将丢弃工作区对指定文件的更改,并将其恢复到最近一次提交的状态。
查看状态和差异
-
git status
:显示工作区和暂存区的状态,如哪些文件被修改、哪些被暂存等。 -
git diff
:展示未暂存的文件更改。--staged
或--cached
:查看已暂存的文件与最后一次提交的差异。--color
:在差异输出中使用颜色高亮显示。--stat
:显示简略的统计信息,包括修改的文件数量和添加/删除的行数。--name-only
:只显示发生更改的文件名,而不显示具体的差异内容。
其他常用命令
git mv <filename> <rename>
:重命名文件,并将已改名文件提交到暂存区。git rm <filename>
:用于从工作区和暂存区中删除一个文件。这个命令会将指定的文件从工作区中删除,并将其从暂存区中标记为待删除。需要注意的是,这个命令并不会直接从版本库中删除文件,只会在下次提交时将其删除。如果想直接从版本库中删除文件,可以使用git rm --cached <filename>
命令。例如,git rm myfile.txt
将从工作区和暂存区中删除名为myfile.txt
的文件。
2.3 Git 暂存区常用操作
撤销更改
-
撤销已暂存的更改:
git reset HEAD <文件名>
:如果已经将某个文件添加到暂存区但想撤销暂存,可以使用这个命令。它将文件从暂存区移回工作区,但保留其在工作区中的更改。这样就可以重新对文件进行修改并重新暂存。git restore --staged <文件名>
:这个命令在较新的Git
版本中用于撤销暂存区的更改。它将暂存区对指定文件的更改撤销,并将文件恢复到最近一次提交的状态。
提交代码到本地仓库
git commit -m "提交信息"
:将暂存区中的文件提交到本地仓库。git commit -m "提交信息" -n
:跳过Git
的提交信息格式检查,允许提交不符合标准格式的提交信息git commit -a -m "提交信息"
:将所有已经使用Git
管理过的文件暂存后一并提交,跳过将文件添加到暂存区的过程。git commit --amend
:commit合并,如果在提交后发现漏掉了几个文件或者提交信息写错了,可以使用这个命令来撤销上一次提交。(这样操作之后,push需要强推)
查看状态、差异、提交历史
git status
:显示工作区和暂存区的状态,如哪些文件被修改、哪些被暂存等。git diff
:展示未暂存的文件更改。git log
:查看提交历史。--oneline
: 以紧凑的单行格式显示提交历史记录,只显示提交的哈希值和提交信息。-p
:显示提交的差差异。-n <num>
: 仅显示最近的<num>
条提交记录,例如git log -n 5
只显示最近的 5 条提交记录。同时可以结合-p
:git log -n 5 -p
--author=<author>
: 仅显示指定作者的提交记录,例如git log --author=John
只显示作者为 John 的提交记录。--grep=<pattern>
: 仅显示包含指定模式(正则表达式)的提交记录,例如git log --grep="bug fix"
只显示提交信息中包含 "bug fix" 的记录。<branch>
: 仅显示指定分支的提交记录,例如git log main
只显示 main 分支的提交记录。--graph
: 在提交历史记录中显示 ASCII 图形表示的分支和合并历史。
还有更多的选项,可以查找专门的文章去学习(有大概的概念,用到的时候再看看即可) git log命令参数详解 - 知乎 (zhihu.com)
打标签
打标签是给重要的提交加上一个有意义的标签,方便日后查看和管理。标签分为轻量标签
和附注标签
,以下是打标签的常用命令:
git tag <tagname> <commitID>
:添加轻量标签(不带id则默认为最新提交)git tag -a <tagname> -m <message> <commitID>
:创建一个附注类型的标签。git tag
:查看所有标签。git show <tagname>
:查看标签指定的版本信息,并显示打标签时的提交对象。git push origin <tagname>
:将标签推送到远程仓库中。git tag -d <tagname>
:删除本地标签git push origin --delete <tagname>
:删除远程标签
分支管理
在进行代码开发和版本控制时,我们通常会创建分支来处理多个功能模块或版本迭代的需求。以下是分支管理的常用命令:
git branch
:显示本地仓库的分支列表。git branch <branch-name>
:创建一个新的分支。git checkout <branch>
:切换到指定的分支。git checkout -b <branch-name>
:新建并切换到新建的分支上。git branch -d <branch-name>
:删除指定的分支。
2.4 Git 仓库间常用操作
克隆代码
-
基本用法 :
git clone <仓库URL>
。这个命令用于克隆远程仓库到本地。 -
选项:
-
--branch <分支名>
或-b <分支名>
:这个选项允许我们克隆特定的分支。通常情况下,git clone
默认会克隆远程仓库的所有分支,但是使用-b
选项可以让我们只克隆指定分支的内容。例如,git clone -b development https://github.com/example/repo.git
将只克隆名为development
的分支。 -
--depth <深度>
:创建一个深度为<深度>
的浅克隆。正常情况下,git clone
会将整个仓库的历史记录都克隆下来,但是使用--depth
选项可以指定只克隆近期的提交,从而减少克隆所需的时间和空间。例如,git clone --depth 1 https://github.com/example/repo.git
将只克隆最近一次提交的内容。 -
--single-branch
:这个选项用于只克隆仓库中的一个分支。通常情况下,git clone
会克隆远程仓库的所有分支,但是使用--single-branch
选项可以让我们只克隆仓库中的一个特定分支,从而减少克隆所需的时间和空间。
-
分支、仓库管理
在进行代码开发和版本控制时,我们通常会创建分支来处理多个功能模块或版本迭代的需求。以下是分支管理的常用命令:
git checkout -b <branch-name> <remote-name>/<branch-name>
:在远程分支的基础上创建新的本地分支。git fetch <remote>
:从远程仓库获取最新的代码,但不自动合并到当前分支。git merge <branch>
:将指定分支的更改合并到当前分支。git push <name> <branch>
:将本地分支推送到远程仓库。git pull <name> <branch>
:从远程仓库拉取指定分支的最新更改。git remote add <name> <url>
:添加一个指向远程仓库的引用git remote show origin
:查看远程仓库的分支情况。git remote -v
:查看远程仓库的列表以及对应的URLgit remote rm <name>
:移除指定的远程仓库
Git
的常用操作基本就是这些,还有更多的操作可以参考 Git
官网 Git - 关于版本控制 (git-scm.com)
在对这些操作有一定理解和实践之后,就可以面对平时大部分的问题了,但涉及到成员间的合作时,难免会出现比较复杂的分支管理问题,想要很好的解决就需要再去学习一些进阶操作了!
三、Git 进阶操作
* git cherry-pick
git cherry-pick <commit/branch>
:选择性地 将某个提交应用到当前分支。
我们通过一个图来理解一下:
我们在 featureB
分支执行了 cherry-pick
命令,将 featureA
中的 C4 C5 提交 "pick" 到了 featureB
分支上。
这里主要是将 cherry-pick
的作用体现出来,大家有一定的理解即可,实际的操作案例会在下一章详细讲述。
如果想要了解更多的细节更多的细节非常推荐看一看阮一峰大哥的博客:
git cherry-pick 教程 - 阮一峰的网络日志 (ruanyifeng.com)
* git rebase
git rebase <base>
:将当前分支的提交移动到 <base>
所指定的基础上(变基 )。
同样的,我们通过一个图来理解一下:
该指令将当前分支的提交"重新"基于指定分支,这样可以使得当前分支的提交历史变得更加清晰。
指令执行时,Git
会找到源分支和目标分支最近的共同祖先 ,然后将目标分支上的提交逐一应用到源分支上,形成新的提交。
- 选项 :
--interactive
或-i
:交互式重新基准,进入交互模式,可以手动编辑每个提交,修改提交信息、删除提交、合并提交等操作。--onto <new_base> <old_base> <branch>
:将当前分支上的一部分提交<old_base>移动到另一个分支<branch>上,同时可以指定新的基础点<new_base>。这在需要重新组织提交历史或者将特定的提交应用到不同的分支时非常有用。--abort
:撤销当前正在进行的变基操作,回到变基前的状态。--continue
:在解决完冲突之后继续进行变基操作。
注意,一定要在对分支治理方案明确的前提下进行 rebase
,防止乱套,甚至对其他开发成员造成影响! 与此同时,对于 --onto
配置项,其实还可以进一步的挖掘。再埋一坑!仔细研究后分享给大家。
子模块
子模块的应用其实可以被其他方式替代,但如今仍存在一定的使用价值,我们先来了解一下子模块的作用: git submodule add <repository> [<path>]
:添加一个子模块到目标项目中。
所谓子模块,可以理解为 A 仓库将 B、C 仓库(比如内部组件库)作为依赖项进行了引用,在开发时不需要关心子项目。
接下来我们再来聊聊子模块的可替代性与不可替代性。
可替代性:
- 使用
monorepo
架构,B、C 没有被其他项目仓库时,可以完全替代子模块。 - 有能力的公司,将 B、C 作为
npm
包发布到独立的包管理平台上,但是存在流程比较繁琐的问题。
不可替代性 :
B、C 还被其他仓库引用,且公司暂无提供独立的包管理平台。
为进一步了解,我们再拿 git subtree
进行比对。
相比于 submodule
,subtree
不会记录子模块信息。开发过程中没有相关的对子模块的管理操作,十分方便,但他的操作并不简单。
可以通过下面整理的表格,横向对比一下:
特点 | Git Submodule | Monorepo | Git Subtree |
---|---|---|---|
代码仓库结构 | 主仓库和子仓库分离,子仓库作为主仓库的子目录 | 所有相关代码都放在同一个仓库中 | 子仓库可以被合并到主仓库的任意目录中 |
依赖管理 | 需要手动初始化子仓库,并进行单独的拉取和更新操作 | 依赖关系直接通过文件夹结构和分支管理 | 类似于 Git Submodule,需要手动合并子仓库的变更 |
历史记录 | 子仓库和主仓库的历史记录相对独立,需要切换到子仓库进行查看 | 所有代码都在同一个仓库的历史记录中 | 子仓库的历史记录和变更会合并到主仓库的历史记录中 |
开发流程和协作 | 可以独立开发和测试子仓库,但可能需要额外的协作工具和流程 | 方便团队内部的协作和共享代码,代码复用性高 | 可以将子仓库的代码和变更集成到主仓库中,协作相对便捷 |
适用场景 | 管理独立的、可复用的代码库或者开源项目 | 大型项目,需要频繁协作和高度代码复用性的场景 | 将子仓库合并到主仓库的特定目录下,保持历史记录完整,适用于多个子仓库的管理 |
想要进一步了解如何实际操作,这里不再详述(太臃肿了),想看实操的话可以阅读大佬们的文章:
submodule:Git中submodule的使用 - 知乎 (zhihu.com)
subtree:Git subtree用法与常见问题分析 - 知乎 (zhihu.com)
工作流管理
工作流管理不涉及具体的 Git
命令,而是一种团队共同遵循的开发流程,如 Git Flow、GitHub Flow、GitLab Flow
等。大家可以根据团队的需要选择适合的工作流程,我们拿 Git Flow
举例。
Git Flow 工作流
Git Flow
是一种基于分支管理的工作流,它定义了一个特定的分支模型和一组规则,用于帮助团队更好地组织和管理代码的开发和发布过程。主要包括以下几种类型的分支:
- Master 分支 (main) :主分支,用于存放随时可以在生产环境中部署的稳定版本。
- Develop 分支:开发分支,用于集成各个功能开发的最新代码。所有的新特性开发都基于这个分支进行。
- Feature 分支 :特性分支,用于开发新功能。每个新功能开发都在单独的分支上进行,然后合并回
Develop
分支。 - Release 分支 :预发布分支,用于准备发布新版本。在这个分支上进行最后的测试和修复问题,并最终合并回
Master
和Develop
分支。 - Hotfix 分支 :热修复分支,用于快速修复生产环境中的问题。从
Master
分支上切出一个临时分支进行修复,然后合并回Master
和Develop
分支。
像 GitHub Flow、GitLab Flow
则是相关团队提倡的一种简化的工作流管理模型,选择合适的工作流取决于团队的需求、项目规模和开发方式。
自定义Git行为
Git
钩子(hooks)存储在 .git/hooks/
用来编写自定义脚本,然后将其放置在相应的钩子文件中。
以下是一些常用的 Git
钩子:
- pre-commit:执行提交操作前触发。常用来进行代码格式检查、代码风格审查、运行静态代码分析等操作。如果脚本返回非零退出码,提交操作将被阻止。
- pre-push:执行推送操作前触发。常用来运行测试、进行代码质量检查或其他验证操作。如果脚本返回非零退出码,推送操作将被阻止。
- post-commit 和 post-checkout:提交或切换分支后被触发。可以使用它们来执行一些后续操作,比如自动化构建、安装依赖等。
- pre-receive:在接收到推送操作时被触发。可以使用它来进行更严格的验证,例如检查提交的代码是否满足特定的标准或规则。
更多的操作详见:
一文带你彻底学会 Git Hooks 配置 - 知乎 (zhihu.com)(下定决心自己也整一篇git钩子的文章👊)
Git属性和Git配置
好家伙,把这些放到这里,单纯考虑到使用 Git
遇到一些奇怪的问题时,有可能需要通过这些配置来修正,还是具体问题具体分析吧🫠~
git config
:用于获取和设置存储库或全局选项的配置变量。git attributes
:用于定义特定于存储库的属性。
Git 补丁
我们先来看看相关的指令含义:
git format-patch <commit>
:生成补丁文件,用于在不同仓库之间传递提交。git apply <patch-file>
:将补丁文件应用到当前分支。
如果觉得补丁文件有点抽象,那我们通过一个案例来理解一下。
假设有两个开发者,Alice 和 Bob,他们在不同的仓库中进行工作。Alice 在她的仓库中做了一些更改并提交了一个新的提交。现在,Alice 想要将这些更改传递给 Bob,使得 Bob 可以将这些更改应用到他的仓库中。
以下是具体的步骤:
-
Alice 生成补丁文件:
- Alice 在她的仓库中运行命令:
git format-patch HEAD~1
。 - 这将生成一个补丁文件,其中包含了 Alice 最新的提交所做的更改。
- Alice 在她的仓库中运行命令:
-
Alice 将补丁文件发送给 Bob:
- Alice 将生成的补丁文件发送给 Bob,可以通过电子邮件、聊天工具等方式进行传递。
-
Bob 应用补丁文件:
- Bob 在他的仓库中运行命令:
git apply <patch-file>
,其中<patch-file>
是 Alice 发送给他的补丁文件的路径。 - 这将应用补丁文件中的更改到 Bob 的仓库中,并修改相应的文件。
- Bob 在他的仓库中运行命令:
通过这样的方式实现了跨仓库的协作和代码共享。
能用的场景确实不多,但总得了解一下~
五、最后的最后
对于 Git
的指令操作介绍终于结束了!但距离熟练运用还有很长的路要走,对于文中一些还没有挖透的点,也会在未来的章节中一一讲明白。
最好的方式还是结合实际的案例进行深入,那么下一章就会结合工作中可能遇见的复杂问题,带大家一起体验一下相关的操作。(工作忙碌,在努力准备中!😢)