参考内容:
Git工作流实践
Git 工作流程
Git三大特色之WorkFlow(工作流)
Git分支管理策略
使用 Issue 管理软件项目详解
GitLab Issue 创建及使用说明
Git 提交规范
Git 是软件开发活动中非常流行的版本控制器类工具。随着项目时间的拉长、项目参与人员的更替、软件不同特性功能的发布,从开发人员角度看会发现工程的提交历史、分支管理等非常混乱,项目管理者会因为需求迭代、bug 修复、版本发布等活动无法与代码提交历史一一对应而头疼,混乱的管理常常导致故障泄漏给客户,所以一套规范的规范的 git 工作流程是非常有必要的。
Git WorkFlow
WorkFlow 的字面意思即工作流,比喻像水流那样顺畅、自然的向前流动,不会发生冲击、对撞、甚至漩涡。工作流不涉及任何命令,它就是团体成员需要自遵守的一套规则,完全由开发者自己定义。
当下比较流行的三种工作流程分别为:Git Flow、GitHub Flow、GitLab Flow。它们有一个共同点:都采用功能驱动开发。其中 Git Flow 出现的最早,GitHub Flow 对其做了一些优化,比较适用于持续的版版发布。GitLab Flow 出现的时间比较晚,所以是综合了前面两个工作流的优点制定的。
Git Flow
git-flow 的思路非常简洁,通过 5 种分支来管理整个工程。
分支
周期
说明
master
长期
主分支,用于存放对外发布的版本,任何时候在这个分支拿到的,都是稳定的分布版
develop
长期
开发分支,用于日常开发,存放最新的开发版
feature
短期
功能分支,它是为了开发某种特定功能,从 develop 分支上面分出来的。开发完成后,要再并入 develop
release
短期
预发分支,它是指发布正式版本之前(即合并到 master 分支之前),我们可能需要有一个预发布的版本进行测试。预发布分支是从 develop 分支上面分出来的,预发布结束以后,必须合并进 develop 和 master 分支
hotfix
短期
bug 修补分支,从 master 分支上面分出来的。修补结束以后,再合并进 master 和 develop 分支
Github Flow
github-flow 可以认为是 git-flow 的一个简化版,它适用于持续部署的工程,直接将最新的功能部署到 master 分支上,不再操作 develop 分支。同时通过 CI&CD 的使用,不再需要额外的 release 和 hotfix 分支。github 还结合了推送请求(pull request)功能,在合并 feature 分支之前通过PR请求其他成员对代码进行检查。
master分支中的任何东西都是可部署的
要开发新的功能特性,从 master 分支中创建一个描述性命名的分支(比如:new-oauth2-scopes)
在本地提交到该分支,并定期将您的工作推送到服务器上的同一个命名分支
当您需要反馈或帮助,或者您认为分支已经准备好合并时,可以提交一个推送请求(PR)
在其他人审阅并签署了该功能后,可以将其合并到 master 中,合并后原来拉出来的分支会被删除
一旦它被合并到 master 分支中,就可以并且应该立即部署
github-flow 最大的优点就是简单,非常适合需要持续发布的产品。但是它的问题也很明显,因为它假设 master 分支的更新与产品的发布是一致的,即 master 分支的最新代码,默认就是当前的线上代码。
但实际可能并非如此,代码合入 master 分支并不代表着它立刻就能发布。比如小程序提交审核以后需要等待一段时间才能发布,如果在这期间还有新的代码提交,那么 master 分支就会与刚刚发布的版本不一致。另外还有一种情况就是,有的公司只有指定时间才会发布产品,这会导致线上版本严重落后于 master 分支。
可以发现针对上述情况,一个 master 分支就不够用了,你可能需要在 master 分支之外新建一个 prodution 分支才能解决问题。
Gitlab Flow
因为 gitlab-flow 出现的比较晚,所以它同时具备前面两种工作流的优点,并且又摒弃了它们存在的一些缺点。它的最大原则叫做上游优先(upsteam first),即只存在一个主分支 master,它是所有其他分支的上游。只有上游分支采纳的代码变化,才能应用到其他分支。
gitlab-flow 分为两种情况,分别适用于持续发布和版本发布项目。对于持续发布项目,它建议在 master 分支以外,再建立不同的环境分支。比如,开发环境的分支是 master,预发环境的分支是 pre-production,生产环境的分支是 production。
开发分支是预发分支的上游,预发分支又是生产分支的上游。如果产环境出现了 bug,这时就要新建一个功能分支,先把它合并到 master,确认没有问题,再cherry-pick 到 pre-production,这一步也没有问题,才进入 production。
对于版本发布的项目,建议的做法是每一个稳定版本,都要从 master 分支拉出一个分支,比如 2-3-stable、2-4-stable 等等。发现问题,就从对应版本分支创建修复分支,完成之后要先合并到 master 分支,然后才能合并到 release 分支。版本记录可以通过 master 上的 tag 来记录。
Issue 使用
Git WorkFlow 主要解决了开发人员的问题,但是对项目管理者问题的解决力度不够,比如客户需求的管理、bug 的跟踪等。Github 和 Gitlab 提供的 Issue 功能可以比较好的解决项目的管理问题。
Issue 中文可以翻译为议题或事务,是指一项待完成的工作,比如一个 bug、一项功能建议、文档缺失报告等。每个 Issue 应该包含该问题的所有信息和历史,使得后来的人只看这个 Issue,就能了解问题的所有方面和过程。
Issue 起源于客服部门。用户打电话反馈问题,客服就创建一个工单,后续的每一个处理步骤、每一次与用户的交流,都需要更新工单,全程记录所有信息。因此,Issue 的原始功能是问题追踪和工单管理,后来不断扩展,逐渐演变成全功能的项目管理工具,还可以用于制定和实施软件的开发计划。
下面以可以免费使用的 Github Issues 来介绍如何使用 Issue。
创建 Issue
每个 Github 仓库都有一个 Issue 面板,如下图所示是新建 Issue 的界面。左侧输入 Issue 的标题和内容,支持 markdown 语法。右侧分别对应四个配置项,将在下面一一进行介绍。
Assignees
Assignee 选择框用于从当前仓库的所有成员之中,指派某个 Issue 的处理人员。
Labels
可以为每个 Issue 打上标签,这样方便分类查看和管理,并且能比较好的使用看板进行查看。一般来说一个 Issue 至少应该有两种类型的 Label,即 Issue 的类型和 Issue 的状态(根据需要可打第三种用于表示优先级的 Label),其中 Issue 的状态可以用来构建敏捷看板。
Milestone
Milestone 翻译过来叫里程碑,它的概念和迭代(sprint)或版本(version)差不多。Milestone 是 Issue 的容器,可以很方便的用来规划一个迭代(版本)要做的事情,也能非常方便的统计进度。
Projects
Projects 是 Github 2020 年 6 月份提供的功能,它就是项目看板的功能,Gitlab 所提供的类似功能为 Issue boards,感兴趣读者可以自行阅读文档了解。
PR&MR
至此,可以发现我们可以使用 Issue 管理需求、bug,Milestone 又提供了迭代(版本)的计划管理,通过 Projects 可以创建敏捷看板,用于关注整体项目的进度。前文 Git WorkFlow 从开发者角度提供了项目管理的工作流程,可以思考一下还差什么问题没有解决?
最后剩下的问题是:每一个 Issue 都需要提交代码/文档进行解决,那代码/文档如何与 Issue 进行关联呢?其实无论是 GitHub 还是 GitLab,都可以很方便地在 Issue 上创建分支,在该分支上解决完 Issue 所对应的问题后,提交远程分支即可发起合并请求,在 Github 中称为 Pull request(PR),在 Gitlab 中则叫做 Merge request(MR)。
Git 提交规范
上文已经说了我们可以对每个 Issue 创建分支,既然是分支,那超过一个 commit 是再常见不过的事情了。一些开发人员所写的提交说明常常是fixbug或者是update等非常不规范的说明。
不规范的说明很难让人明白这次代码提交究竟是为了什么。在实际工作中,一份清晰简洁规范的 commit message 能够让后续的代码审查、信息查找、版本回退都更加高效可靠,因为我们还需要对提交说明制定一套规范。
那么什么样的 commit message 才算是符合规范的说明呢?不同团队可以制定不同的规范,此处推荐使用 Angular Git Commit Guide
提交格式指定为提交类型(type)、作用域(scope,可选)和主题(subject),提交类型指定为下面其中一个:
类型
说明
build
对构建系统或者外部依赖项进行了修改
ci
对 CI 配置文件或脚本进行了修改
docs
对文档进行了修改
feat
增加新的特征
fix
修复 bug
pref
提高性能的代码更改
refactor
既不是修复bug也不是添加特征的代码重构
style
不影响代码含义的修改,比如空格、格式化、缺失的分号等
test
增加确实的测试或者矫正已存在的测试
作用域即表示范围,可以是任何指定提交更改位置的内容。主题则包括了对本次修改的简洁描述,有以下三个准则:
使用命令式,现在时态:“改变”不是“已改变”也不是“改变了”
不要大写首字母
不在末尾添加句号
下图是 NocoBase 的 commit message 截图,可供参考
Read More ~
标签:#
Git
Git 基本原理及常用命令速查
参考内容:Pro Git book
如果你只是想查看 Git 常用命令可以选择直接到文章底部「Git 常用命令」阅读,文章大部分内容是 Git 进阶知识,均是自己的读书笔记,如果还想在此基础上再上一层楼,那可以直接看 Pro Git book。
Git 历史
版本控制器是一种记录一个或若干文件内容变化,以便将来查阅特定版本的修订情况。也就是说,版本控制器记录了一个可供考证的历史数据,通过该数据可以知道文件是怎么一步一步发展到今天这个样子的。
最初 Linux 项目使用 BitKeeper 来管理和维护代码,但是到了 2005 年,开发 BitKeeper 的商业公司同 Linux 内核开源社区的合作关系结束,他们收回了免费使用 BitKeeper 的权力。那 Linux 开源社区的解决方案就是自己搞一个版本控制器,所以就有了 Git。
简单说就是 Linus 被逼的去开发了这一款叫做 Git 的版本控制器,因为 Linus 本身就是内核专家与文件专家,所以 Git 也就自然而然具备了非凡的存储能力与性能。
安装
关于如何安装 git 可以查看 Pro Git book,安装完成后需要进行一些必要的配置,比如用户信息、文本编辑器、差异分析工具等等,我们可以通过git config --list来查看配置信息。比如我们要配置用户和邮箱,就可以像下面这样输入命令。
$ git config --global user.name "John Doe"
$ git config --global user.email johndoe@example.com
Git 原理
Git 和大多数版本控制器有一个重要的区别,就是它直接记录快照,而非差异比较,其它大部分系统以文件变更列表的方式存储信息,而 Git 则存储每个文件与初始版本的差异。换句话说,只要你的文件有改动,那么 Git 就会将该文件复制一份,正因为 Git 的这个特性,所以 Git 仓库很容易就变得非常大;为了高效,如果文件没有修改,那么 Git 不再重新存储该文件,而是只保留一个链接指向之前存储的文件。Git 对待数据更像是一个快照流。
Git 有三个区,分别为:仓库、工作目录、暂存区。基本的 Git 流程为:1)在工作目录中修改文件;2)暂存文件,将文件的快照放入暂存区域;3)提交更新,找到暂存区域的文件,将快照永久性存储到 Git 仓库目录。那么相应的 Git 就有三种状态:已提交(committed)、已修改(modified)和已暂存(staged),你的文件可能处于其中之一。
Git 基础
工作目录中的文件不外乎处于两种状态:已跟踪或未跟踪。已跟踪是指那些纳入了版本控制的文件,在上一次快照中有它们的记录;工作目录中除了已跟踪文件以外的所有文件都属于未跟踪文件,们既不存在于上次快照的记录中,也没有放入暂存区。
查看文件状态
如果需要查看哪些文件处于什么状态,可以使用git status命令,这个命令显示的信息十分详细,如果你喜欢简洁一点的信息,那么可以在其后添加一个-s,其报告格式类似于下面这样。
$ git status -s
M README
MM Rakefile
A lib/git.rb
M lib/simplegit.rb
?? LICENSE.txt
??表示新添加的未跟踪文件;修改过的文件前面有M标记,右边的表示还没有放入暂存区,左边的表示已经放入暂存区了。当然你可能不希望每个文件都出现在未跟踪列表中,比如编译过程临时创建的文件、日志文件等等,所以可以通过创建一个名为.gitignore 的文件,列出要忽略的文件模式,它支持标准的glob模式匹配(shell 所使用的简化了的正则表达式),在 gitignore 中有一个十分详细的针对数十种项目及语言的.gitignore文件列表。
git status对于具体修改显示的过于模糊,如果想查看具体修改了什么地方,可以使用git diff命令,比如git diff README.md。需要注意的是git diff本身只显示尚未暂存的改动,而不是自上次提交以来所做的所有改动,如果需要查看已经暂存起来的变化,则要加上--staged或者--cached,比如git diff --cached README.md。
删除文件
当然我们不可避免的需要删除某个文件,如果你仅仅是简单的从工作目录中手工删除文件,那它并没有真正的从 Git 中删除,Git 会将这次删除识别为一次改动。更好的方式是使用git rm命令来完成删除文件的工作,比如git rm README.md就会从已跟踪文件中删除,并且连带从工作目录中删除指定文件。
如果删除之前修改过并且已经放到暂存区域的话,则必须要用强制删除选项-f(译注:即 force 的首字母)。 这是一种安全特性,用于防止误删还没有添加到快照的数据,这样的数据不能被 Git 恢复。
另外一种情况是,我们想把文件从 Git 仓库中删除(亦即从暂存区域移除),但仍然希望保留在当前工作目录中。 换句话说,你想让文件保留在磁盘,但是并不想让 Git 继续跟踪。 当你忘记添加 .gitignore 文件,不小心把一个很大的日志文件或一堆 .a 这样的编译生成文件添加到暂存区时,这一做法尤其有用。这时就需要使用--cached选项了,比如git rm --cached README。
查看历史
我们或许因为某种原因需要回顾一下提交历史,这时git log就派上用场了,默认不用任何参数的话,git log会按提交时间列出所有的更新,最近的更新排在最上面,这个命令会列出每个提交的 SHA-1 校验和、作者的名字和电子邮件地址、提交时间以及提交说明。
git log提供的选项很多,更详细的内容可以查看 Git 基础 - 查看提交历史。除了不带选项的命令,我个人更常用的命令还有另外两个,分别为:git log --pretty=oneline它将每个提交放在一行显示,在查看的提交数很大时非常有用;git log --graph或者git log --pretty=oneline --graph用于显示 ASCII 图形表示的分支合并历史。
撤销操作
在任何一个阶段我们都可能有想要撤销的操作,我们只需要掌握几个基本的撤销操作就能够应对日常的工作了。
第一种情况:取消上一次提交。有时候当我们提交完之后才发现漏掉了几个文件没有添加,或者是提交信息写错了,此时可以使用带--amend选项的提交命令尝试重新提交,即git commit --amend。这个命令会将暂存区的文件全部提交,如果自上次提交以来你还没一做任何修改(比如,在上次提交后马上执行了此命令),那么快照将会保持不变,而所修改的只是提交信息。
第二种情况:取消暂存的文件。假设你修改了两个文件并且想要将它们作为两次独立提交,但是却不小心输入了git add *暂存了它们两个,如何取消其中一个暂存呢?其实在运行git status时已经给出提示了。
$ git status
On branch master
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
renamed: README.md -> README
modified: CONTRIBUTING.md
所以如果我们想要取消CONTRIBUTING.md的暂存,那么就可以用git reset HEAD CONTRIBUTING.md命令来完成。
第三种情况:撤销对文件的修改。有时候我们可能并不想保留对某个(若干)文件的修改,git status也给出了详细的提示,告诉我们如何将文件还原成上次提交时的样子,即git checkout -- <file>,比如输入命令git checkout -- CONTRIBUTING.md,就会将CONTRIBUTING.md重置到上一次提交时的样子。
需要注意的是git checkout -- <file>是一个比较危险的命令,因为它仅仅是拷贝了另一个文件来覆盖当前文件,所以你对那个文件的所有修改都会消失,而且不可恢复。
远程仓库
前面我们都是在讲本地操作,远程仓库的使用是必不可少的技能。可以使用git remote命令查看每一个远程服务器的简写,对于已经克隆的仓库,它至少会包含一个origin,这是 Git 给克隆仓库服务器取的默认名字,它和其它服务器并没有什么区别,只是很少人会去修改这个默认名字而已。
如果想要给一个远程仓库重新取一个简写名,那么可以运行git remote rename来完成,比如git remote rename pb paul就是将pb重命名为paul。值得注意的是这样同样也会修改你的远程分支名字,那些过去引用pb/master的现在全引用paul/master。
当想要将自己的成果分享给他人时,就需要将其推送到上游,使用git push [remote-name] [branch-name]即可,比如你想要将master分支推送到origin服务器时,就可以运行git push origin master。
除了分享自己的成果,我们也需要获取他人的成果,即从仓库拉取自己没有的信息,比如git fetch origin,需要注意的是git fetch命令会将数据拉取到你的本地仓库,但它并不会自动合并或修改你当前的工作,所以你还需要git merge来合并分支,实际上有一个git pull命令可以帮我们把这两个步骤都做了,你可以简单的将git pull理解为git fetch后面紧接着一个git merge。
分支管理
Git 的分支模型是它的必杀技特性,它处理分支的方式是难以置信的轻量,创建分支几乎是在一瞬间完成,而且在不同分支间的切换也非常的便捷,要理解 Git 的分支,我们必须要再次回顾 Git 是如何保存数据的。
下图是我们的一个工作流,可以看到所谓的分支实际上就是一个可以移动的指针而已,master、v1.0都仅仅是一个指针,而创建分支、切换分支等操作也都只是对指针的操作,因此就不奇怪为什么 Git 这么快了。
那么 Git 又是如何知道当前在哪一个分支上呢?它仅仅是用了一个名为HEAD的特殊指针,你可以将HEAD想象为当前分支的别名,HEAD指向哪个分支,就表示当前处于哪个分支。
分支创建与切换
我们可以使用git branch [branch-name]来创建一个新的分支,比如git branch testing;如果使用不带选项的git branch,那么它会列出当前所有的分支,这里需要注意的是master分支也不是特殊分支,它是运行git init时自动创建的默认分支,因为大家都懒得去改它,所以它就好像变得特殊了一样。
git branch [branch-name]只是创建了一个新分支,并不会切换到这个分支上面去,分支的切换说白了就是移动HEAD指针,我们只需要使用git checkout testing就可以切换到testing分支上去了。
当然我们可以使用git checkout -b [branch-name]来创建一个分支并同时切换到这个分支,把这个命令与git commit -a -m来对比,你就会发现它们的类似之处。
分支的合并与删除
当我们零时在一个新分支上解决了问题后,需要将其合并到master分支,只需要切换到master再运行git merge命令即可,Git 会自动找到这两个分支的共同祖先,然后做一个简单的三方合并。
当然理想情况下是直接合并成功,但是不免会遇到合并冲突的情况,一旦遇到冲突了,Git 会像下面这样来标记冲突内容,你需要做的是选择由=======分割的令部分的其中一个或者自行合并,当<<<<<<<,=======,和>>>>>>>这些行被完全删除了,你需要对每个文件使用git add将其标记为冲突已解决。
<<<<<<< HEAD:index.html
<div id="footer">contact : email.support@github.com</div>
=======
<div id="footer">
please contact us at support@github.com
</div>
>>>>>>> testing:index.html
当合并完分支后,之前的分支一般就不会再要了,这时你可以运行git branch -d [branch-name]来删除指定分支,比如使用git branch -d testing来删除testing分支。
远程分支
远程分支以(remote)/(branch)的形式来命名。如下图所示,如果你克隆一个仓库下来,那么这个仓库除了会有一个本地的分支指针,还会有一个远程分支指针。如果你在本地的master分支做了一些工作,但是你并没有与origin服务器连接,那么你的origin/master指针就不会移动。
在这之前我们已经讲过通过推送分享自己的成果,在运行git push origin master命令时,Git 会自动的将master分支名字展开为refs/heads/master:refs/heads/master,即意味着推送本地的master分支来更新远程仓库上的master分支,所以你也可以运行git push origin master:testing来做类似的事,如果远程仓库没有testing分支,那它会自己创建一个新的testing分支。
我们肯定需要创建一个跟踪远程仓库的其它分支,最简单的就是运行git checkout -b [new-branch] [remote-name]/[branch],该命令会以远端[branch]分支的内容来创建本地的[new-branch]分支,Git 也对该命令做了一个简化,git checkout --track [remote-name]/[branch],该命令就会在本地创建一个[branch]分支用于跟踪远端的[branch]分支。
当然,我们还需要了解一个删除远程分支的命令git push origin --delete [branch],需要注意的是这个命令基本上只是从服务器上移除这个指针。 Git 服务器通常会保留数据一段时间直到垃圾回收运行,所以如果不小心删除掉了,通常是很容易恢复的。
Git 常用命令
挑了一些比较重要 Git 命令,我把个人常用的命令使用代码块标记出来了。
命令
作用
git init
将一个目录转变成一个 Git 仓库
git clone
从远程克隆一个仓库到本地,它是多个命令的组合,
git add
将内容从工作目录添加到暂存区
git commit
将暂存区文件在数据库中创建一个快照,然后将分支指针移到其上
git commit -a -m [msg]
git add和git commit的组合
git status
展示工作区及暂存区域中不同状态的文件
git status -s
比git status展示的内容更加简洁
git diff
对比工作目录文件和暂存区快照之间的差异
git diff --cached
对比已暂存的差异
git reset
根据你传递给动作的参数来执行撤销操作
git rm
从工作区,或者暂存区移除文件
git clean
从工作区中移除不想要的文件的命令
git checkout
切换分支,或者检出内容到工作目录
git branch
列出你所有的分支、创建新分支、删除分支及重命名分支
git checkout -b [branch]
创建新分支并切换到该分支
git log
展示历史记录
git log --pretty=oneline
简洁版历史记录
git merge
合并一个或者多个分支到已检出的分支中
git stash
临时地保存一些还没有提交的工作
git pull
git fetch 和 git merge 命令的组合体
git push
将本地工作内容推送到远程仓库
git push origin local_branch:remote_branch
比git push更加详细的推送
git checkout --track [remote-name]/[branch]
在本地创建一个分支用于跟踪远程同名分支
git remote -v
显示所有远程仓库地址
git remote set-url origin [url]
设置远程仓库地址为url
Read More ~