我深信,独立开发模式下的开发者们是不需要过多操心我们的代码管理的,更不会担心我这一次的合并会不会有冲突? 有冲突了我怎么去改? 代码改乱了我怎么办?之类的问题。

Git 的前世今生

代码的版本管理工具有很多,它们从性质上分为:商用与开源,从管理方式上分为:集中式与分布式。商业以 BitKeeper 为代表,开源首推 SVN 与 Git。而现在 Git 成为最受欢迎的代码管理工具之一,离不开我们熟知的 Linux 之父 Linus 以及全世界为开源工作的开发工程师们的共同努力。

在两个星期之内,Linus 用 C 语言完成了 Git 的初代版本,一个月之后 Linux 的源码由 Git 接管。

Git 的快速风靡为开源世界带来的变化,在我们现在的日常工作中深有体现,比如:Github、GitLab、Gitee、GitBook。从命名上看来,我们可以猜测它们都与 Git 有关,实际上它们都是基于 Git。尤其是 Github,上面聚集着大量的开源项目,为开源项目提供着免费的托管服务。

一切的一切都让我对开源才是未来这个观点深信不疑。

workflow 工作流

既然使用了 Git,我们的代码管理还是遵循 Git 的工作流吧。Git 与 Linux 一样,随处体现着开源项目的无限魅力,让每一个开发者心中都有了一个属于自己的 Git 或者是 Linux,其魅力主要体现在多样性、扩展性。

workflow 工作流有以下三种:

  1. git flow
  2. github flow
  3. gitlab flow
Git Flow

既然在 git 中存在着多种管理方式,那么每一个团队都需要寻找适合自己团队的代码工作流。我们前端团队选择的是gitflow工作流,其示意图如下:



gitlab上撤销一次提交_git 使用

其特征是:始终保持两个常驻分支,其他分支即为辅助分支,辅助分支合并之后可以随时删除,并不影响其常驻分支。

Github Flow

GithubFlow 工作流的管理方式相当特殊,它只有一个分支 master,而且受到了保护,有一个专门维护的开发者负责核查合并每一个 pr。每一次 pr 操作之后,设置的 github actions 自动触发进行 CI 操作,通过后即可发布 relaese 版本。



gitlab上撤销一次提交_工作区_02

Gitlab Flow

Gitlab Flow 工作流是一个混合了 GitFlow 与 GithubFlow 特性的新生代工作流,从常驻分支 Master 和 Dev 中检出一个 release 预发布分支,业务代码发布上线之后稳定之后将代码合并入 Master 分支,并且打上一个 Tag 版本号。于是我们的代码终于可以配合一些管理平台,快速部署了。

实战 GitFlow

现在我们亲手实现一个简单的 GitFlow 工作流,一起动手吧~~

git branch

git branch -a

git branch

git branch -a



gitlab上撤销一次提交_工作区_03

git branch 可以查看本地的全部分支,git branch -a 可以查看该源上的全部分支,若要查看远程的全部分支可以使用, 如下图所示,前缀以 remote/origin 开头的即为远程分支名。

git branch -r

git branch -r



gitlab上撤销一次提交_gitlab上撤销一次提交_04

origin 为当前代码的远程源之一,如果要查看全部源可以使用

# 查询关联源
git remote
#$ origin

# 查询源信息
git remote get-url origin
#$ git@gitlabl.com



# 查询关联源
git remote
#$ origin

# 查询源信息
git remote get-url origin
#$ git@gitlabl.com

由上图可见,我们本地已经有了 master 分支了,gitflow 规范 dev 分支的上游源只有 master,所以我们需要从 master 分支检出一个 dev 分支

git checkout master

# parent branch master
git checkout -b dev

git checkout master

# parent branch master
git checkout -b dev

好啦,现在我们的常驻分支已经全部在我们的工作区啦,现在我们需要管理我们的团队代码啦,我们的前端代码以功能模块区分,新功能以 feat-*开头, 其操作如下:

git checkout dev

git checkout -b feat-module-name

git checkout dev

git checkout -b feat-module-name

现在我们组内的一个前端开发人员在 feat-module-name 下开发一个混合查询功能,另外一个前端开发需要开发一个新增以及编辑的表单,所以他需要从 dev 检出一个 feat-module-form

现在我们以这两个功能作为我们工作流的示范。

开发混合查询的前端工作的效率很高,功能很快就开发完成,通过了内测现在进行到了提测阶段,所以我们需要一个辅助分支 test 用来配合测试环境。现在我们需要将查询功能合并进入 test 分支啦。

初始版本的 test 分支应该由 dev 分支检出

现在我们将第一个新功能进入测试环境

git checkout test

# 不要使用快速合并
git merge feat-module-name --no-ff

git checkout test

# 不要使用快速合并
git merge feat-module-name --no-ff

现在我们的另一个功能的开发也开发完成啦,也需要进测试分支啦,同理我们合并进入test

git checkout test

git merge feat-module-form --no-ff

git checkout test

git merge feat-module-form --no-ff

成功合并的提示并没有如期的出现,反而是出现了一串英文,我们可能不认识全部,但是绝对认识一个单词, 它就是团队开发的口头禅: 卧槽 又冲突了, 没错它就是 CONFLICT, 翻译一下就是冲突



gitlab上撤销一次提交_git_05

如上图: 是由于我们两个前端在两个分支中同时修改了 index.js 文件,命令行中也指出了我们冲突的文件位置,那么我们应该怎么修复冲突才算是正确操作呢?

gitlab上撤销一次提交_git_06

首先不要轻易的选择 Accept Current Change / Accept InComing Change / Accept Both Change。我们必须有相当清晰思路,现在我们是两个新功能先不考虑 Bug 修复的代码冲突,查看一下冲突代码前后文是否有相关联的代码,再决定怎么解决我们的冲突。现在我们将模拟代码全部保留, 记住解决冲突之后一定要将冲突文件再次提交,最后才是推送远程。

我们回到测试分支,现在的测试分支有了两个新功能了,我们可以通知测试工程师们,可以跑一下脚本啦,代码已经上测试啦。

经过第一轮功能测试之后,测试工程师们提交了好几个 Bug,首先是 feat-module-name 有一个条件查询无效,其次 feat-module-form 表单重新编辑提交报错啦。

好吧有了 bug 了,我们再次回到我们的工作分支 feat-module-name 处理了 bug 之后合并测试,feat-module-form 也处理完成了,合并之后推送远程测试分支,进行第二轮测试。这一次测试很顺利,我们的功能通过了测试,我们的代码也要合并进入 dev 分支啦。

操作与发布测试环境相同,代码成功进入了 dev 分支。现在我们的测试工程师需要进行回归测试,针对于全部功能点来一次回归。

Git 版本回退

但是现在出了一点状况, 我们的混合查询功出问题了,时间上已经来不及了,所以我们选择这一次部署,要移除这个查询功能。要实现这样的一个功能,就需要使用 Git 的版本回退指令,Git 的版本回退操作,基本上有以下三种:

  1. reset
  2. rebase
  3. revert

那我们一次介绍一下它们对应的操作说明。

1. reset

reset 指令应该是我们日常用到的最多的一个指令,它可以快速撤销已提交到暂存区的文件到工作区 git reset [path],如果指定了文件地址,就只会撤回该文件地址相对应的文件,不指定就会将暂存区文件全部撤回。如图标红区:

gitlab上撤销一次提交_工作区_07

# 回退两个提交
git reset --hard HEAD~~


# 回退两个提交
git reset --hard HEAD~~

执行--hard,然后 commit 之后会将当前指针之后的全部 commit 舍弃,而--soft 不会,在修改 commit 之后,它仍然会在保留提交的历史记录。

同为reset指令,但是它们的使用情景不相同, 比如:

  1. 你已经将一次错误修改commit到了本地暂存区,你已经确认不再需要这一次的代码提交了,可以直接选择 --hard 丢弃本次 commit_id,回到工作区的代码将会是 HEAD~~ 指针的代码
  2. 你提交了一次错误修改,但是不需要全部丢弃本次 commit 的代码,只需要改一下再次提交就可以了,那么你可以选择 --soft 回到工作区,此时本次 HEAD 的代码更改仍然存在,修改之后提交,完美。
2. rebase

rebase翻译一下是重定、变基的意思。使用rebase指令我们可以完成一些比较骚气的操作,比如将我们开发分支的全部提交合并为一次指定提交。除了可以合并多次提交丢弃一些无用的提交记录之外,还可以帮助我们将我们的提交历史整个变为一条时间提交线。下面我们分开演示一下:

  1. 合并多次提交 我们可以去查看一下 git rebase 的文档,有一个具有交互性的指令git rebase -i
git rebase -i HEAD~~~~

git rebase -i HEAD~~~~

我 pick 了最近的一次提交,将其他的 commit 全部改为 f 后,现在提交已经全部合并进入了 pick 的 top_commit_id 了。这样我们可以将我们的功能分支全部管理起来,在合并进入 dev 分支之前将我们的代码处理的干干净净。

其操作结果如下图:

gitlab上撤销一次提交_gitlab上撤销一次提交_08

将代码处理干净了,没有多余提交了,我们可以来美化一下我们的 master 以及 dev 分支上面的历史提交记录啦。

在开始之前我们查看一下官网的描述, 关键的流程图已经做出了标记,如下图:

gitlab上撤销一次提交_工作区_09

上图的表达的意思是在 E 节点检出了一个分支,功能开发完成之后merge进入master分支就会有一个分叉,而使用了rebase之后,将原 A、B、C 节点隐去,将 A'、B'、C'直接合并到master分支的 G 提交后。结合节点图二,表达的是如果两个分支中同时存在一个相同提交,变基之后原节点被隐藏,改为该节点副本,其他提交依次拼接到master最近一次提交。

操作一下,这个时候我们需要格式化一下我们的 log 日志,试一下吧。

git log --graph --pretty=oneline

git log --graph --pretty=oneline

进入主题,我们直接进入变基操作,看我操作

# 先回到dev 分支进行一个检出
git checout -b feat-module-rebase
# 修改文件之后提交
git add .
git commit -m'feat(index): create rebase flag'

# 进入 feat-module-rebase 分支后立即进行变基
git rebase dev

git merge feat-module-rebase

# 先回到dev 分支进行一个检出
git checout -b feat-module-rebase
# 修改文件之后提交
git add .
git commit -m'feat(index): create rebase flag'

# 进入 feat-module-rebase 分支后立即进行变基
git rebase dev

git merge feat-module-rebase

其操作结果如下图:

gitlab上撤销一次提交_gitlab上撤销一次提交_10

我们分别看一下变基的feat-module-name分支与未变基的feat-module-form分支

feat-module-name

gitlab上撤销一次提交_git 使用_11

feat-module-form

gitlab上撤销一次提交_工作区_12

由图上可以看出,我的 rebase 操作是基于 dev 分支,所以 feat-module-name 以及 feat-module-form 两个分支的提交全部合并到了 dev 分支上,它们没有了分叉,是一条笔直的直线。

rebase 操作与 reset 操作都会改变仓库的提交记录,对日志都是毁灭性的打击,所以这个指令是不太推荐使用的。

3. revert

revert 操作其指针永远是前进的,其翻译是反转,它只会生成一个全新的 commit 去假回退对应指针, 修改之后提交其历史提交记录不会更改,不会像 reset --hard 直接移除提交记录,而是与 reset --soft 类似,所以它的操作永远是安全的,我们对于远程仓库的管理比较推崇 git revert

我们看一下revert的操作示意图:

gitlab上撤销一次提交_gitlab上撤销一次提交_13

让我们来试一下 revert 操作吧,我已经提前准备好了三次提交,如下如,我们将每一次的提交标记清楚:

gitlab上撤销一次提交_gitlab上撤销一次提交_14

现在我认为第一次提交有错误代码,我需要注释它,操作如下:

```bash
git revert HEAD~~
```

```bash
git revert HEAD~~
```

没想到吧,报了一个冲突,没关系前后多次版本的提交有冲突是很正常的,修复之后提交,然后再次执行一次 revert 操作,可以看下面的历史记录,多次以 First 为基准进行 revert 操作,修改之后提交,会有一条新的 commit 提交记录,所以它的指针永远是向前走,以此来达到一种反转效果的回退。

gitlab上撤销一次提交_新功能_15

Revert 还可以执行一个骚操作,我们 Revert 了一次,达到了移除指定 commit_id 的操作,现在推迟发布上线,已经有时间去改了,我又需要将 commit_id 的操作加上去。我们可以执行对一个 Revert 执行一次 Revert,将指定的反转再一次反转,这样我们的代码不就还原啦。操作如下:

```bash
git log --oneline -10
# 查找全部提交记录, 找出其中的需要revert的revert_id

git revert revert_commit_id
```

```bash
git log --oneline -10
# 查找全部提交记录, 找出其中的需要revert的revert_id

git revert revert_commit_id
```

执行之后查看一下代码,如果顺利的话,指定 revert_commit_id 的提交应该已经还原了。

线上问题

相当不巧,现在线上出现了一点点小问题,需要紧急修复一下,按照规范,我们需要从 dev 分支检出一个新的分支以hotfix-*命名。操作如下:

git checkout dev

git checkout -b hotfix-form

git checkout dev

git checkout -b hotfix-form

代码修复完成之后我们按照提测流程,将代码再次合并入 test 分支测试,测试环境通过后代码进入 dev 分支,这一次的代码将在预发布环境再次测试,预发布环境测试通过后紧急部署到生产环境。

一个完整的 gitflow 工作流到此就已经闭环,在多次流程之后,各个团队的测试通过的功能代码就已经全部进入了 dev 分支。而未通过测试功能代码依旧停留在 test 分支上,我们可以在预发布决断这一次迭代需要发布哪一些功能,因为我们可以在 dev 分支合并选择需要的功能分支的代码。

注意:1. 以 master 分支为主分支,受到分支保护,不允许任何人提交,由专人维护2. 全部分支以 dev 分支为上游源

相关文章:Git 协同与提交规范[1](蚂蚁金服前端九部 "Git 协同与提交规范][1")Rebase(变基)[2](跟着廖雪峰学习 Git)Rebase 变基[3](Git 官网)为什么变基操作是危险的?[4]

参考资料

[1]


Git 协同与提交规范: https://www.yuque.com/fe9/basic/nruxq8#df368884

[2]


Rebase(变基): https://www.liaoxuefeng.com/wiki/896043488029600/1216289527823648

[3]


Rebase 变基: https://git-scm.com/book/zh/v2/Git-%E5%88%86%E6%94%AF-%E5%8F%98%E5%9F%BA

[4]


为什么变基操作是危险的?: http://jartto.wang/2018/12/11/git-rebase/