likes
comments
collection
share

基于Git的主干开发工作流

作者站长头像
站长
· 阅读数 5

一、 SVN 与 Git 对比

首先,SVN 是一个集中式的版本控制系统,Git 是一个分布式的版本控制系统,这是目前二者最大的区别之一。 基于Git的主干开发工作流

如上图所示:对于很多操作来说,SVN必须在联网下进行,而Git则在本地进行即可。另外,SVN有基于目录级别的权限管理,而Git则需要借助gitlab实现细粒度的权限管理。 基于Git的主干开发工作流 图 2.SVN 记录版本变化的方式 基于Git的主干开发工作流 图 3.GIT 记录版本变化的方式 另外一个原理上的区别是如何保存不同版本间的差异:以 SVN 为代表的集中式版本控制系统保存的是文件不同版本间的变化的那一部分,而以 GIT 为代表的分布式版本控制系统直接保存的是版本文件的新的一个快照。

1.1 分支

SVN: SVN的分支仅仅是一些有特殊含义的目录。在创建一个新的分支时,你只是把项目的当前状态完完整整地拷贝到这个新的分支目录中,创建完分支后,影响全部成员,每个人都会拥有这个分支。 Git: Git的分支技术是它的设计核心,因此它拥有一个完全不同的概念。一个在 Git 中的分支就是一个指向某次所提交版本的指针( commit 对象的可变指针):不拷贝任何文件,不创建任何目录,没有任何额外的操作。 在 Git 中你当前永远工作在一个分支上,至少工作在那个系统默认创建的“master”分支上。在你的工作副本上只包括你当前的活动分支中的文件( Git称之为“HEAD”)。所有其他的版本和分支都被保存在你的本地仓库中,并且随时都可以非常快速地恢复到一个旧的版本。一定要记住 Git 的分布式特性,分支可以被发布到在远程服务器上,但是本地上的分支对于日常的工作更加重要,git自己本地创建的分支不会影响其他人。

1.2 提交

SVN: 当你想要在 SVN 中提交一个改动,有如下的一些规则: 你必须确保与中央仓库的连接。你不能进行离线提交。 提交的内容要立即存储在中央仓库中。 它会被分配一个全局递增版本号。SVN具有一个非常重要的特性就是它的信息从不丢失,即使当你删除了文件或目录,它也许从最新版本中消失了 ,但这个对象依然存在于历史的早期版本中。 Git: 提交在 Git 中就是完全另外一种情况: 你没有必要连接到任何一个 “中央” 仓库,因为在你的计算机中就拥有一个完整的本地仓库。因此提交仅仅只记录在本地仓库上。它们不会自动地传递到远程仓库中,除非你自己决定共享这个改动。 文件的改动并不意味着它会被自动地包含在下一次提交中。你必须指明哪些改动你想要提交,并把它添加的所谓的 “暂存区(Staging Area)”中。你甚至可以只对文件的部分修改或是特定的几行代码进行提交,而其他部分则稍后提交。“commit hashes” 替代了版本号码。由于提交都发生在开发人员的本地计算机上,你不可能给某个提交分配一个号码 #5,而另外一个分配 #6,这就产生了个问题,在分布式系统下谁是第一个提交呢?在 Git 中,每一个提交必须拥有一个唯一的ID,因此一个哈希字符串就代替了那个依次递增的版本号。Git可以丢弃最新的一个或几个提交,使用 git reset –hard命令可以永远丢弃最新的一个或者几个提交。Commit History 历史记录,存储着所有提交的版本快照,并由当前分支引用的指针HEAD指向该分支最新一条提交。Git没有一个全局版本号,而SVN有:目前为止这是跟SVN相比Git比较明显的区别。并且通过使用git rebase命令代替git merge命令操作,可以更好的保持整洁的线性提交记录。

1.3 分享工作

SVN: 在 SVN 中,在提交之后,你的工作会被自动地转移到中央仓库上去。只有在你连接到这个中央服务器时你才可以进行提交。 Git: Git 不会自动上传任何东西。你可以自己决定你的哪些分支需要共享给你其他的团队成员。除此之外共享工作也是十分安全的。冲突只会出现在你的本地上,它决不可能发生在远程服务器上。这会让你有信心来解决冲突,因为你不会破坏远程仓库。

更多详细区别请参考: 1. Git与SVN对比 2. Git与SVN对比 3. Git与SVN对比

二、 Git核心概念及操作

2.1 Git对象

Git 的核心部分是一个简单的键值对数据库, 你可以向该数据库插入任意类型的内容。你可以在 .git/objects 目录下看到一些文件,这就是Git 存储内容的方式——一个文件对应一条内容。每次我们运行 git add 和 git commit 命令时, Git 所做的实质工作——将被改写的文件保存为数据对象,更新暂存区,记录树对象,最后创建一个指明了顶层树对象和父提交的提交对象。 这三种主要的 Git 对象——数据对象树对象提交对象——最初均以单独文件的形式保存在 .git/objects 目录下。 基于Git的主干开发工作流

2.2 工作区域

如下图所示:Git工作区域分为三部分,工作目录(Working Directery)暂存区(Staging Area)Git仓库( Repository) 基于Git的主干开发工作流 工作目录(Working Directery): 是电脑中实际的目录,一般是从Git仓库压缩数据当前版本中解压出来的文件列表。所以你在本地磁盘看到的你项目源码的文件列表,其实就是Git开放给你的一个沙盒。在你将文件的修改添加到暂存区域并将快照记录到提交历史(本地仓库)之前,你可以随意更改。可通过git add(可多次)将修改添加到暂存区以准备一起commit到本地仓库 ,也可以直接通过 git commit -a 选项,Git 就会自动把所有已经跟踪过的文件暂存起来一并提交,从而跳过手动git add 的步骤。 暂存区(Staging Area): 类似于缓存区域,它是一个文件临时保存你的改动,保存着即将提交的文件列表快照。可通过git commit将所有缓存区的内容提交到本地仓库。 仓库(Repository): 分为本地仓库和远程仓库,本地仓库等待合适的时机(功能完成/提交补丁/持续集成功能),通过git push 将本地仓库代码的变化同步更新到对应的线上代码仓库(远程仓库)以供别人或其他分支通过git pull拉取合并。

2.3 Git操作

以上几块区域间的数据流动操作大致如下图所示,更多操作命令请查阅 git文档基于Git的主干开发工作流 暂存相关操作: 将(文件)修改添加到暂存区:git add fileName.xx 或 git add --all(所有文件) 查看暂存区文件列表:git ls-files --stage 查看暂存区文件内容:git cat-file -p f48r4s(指向文件的哈希值指针) 对比工作目录与暂存区(文件):git diff [fileName.xx](省略则对比所有) 撤销暂存区的(文件)修改:git reset HEAD fileName.xx 或 git reset .(所有文件) 提交相关操作: 将暂存区内容提交到本地库:git commit [fileName.xx](省略则提交所有) [-m '这里可以写提交信息'] 查看本地提交记录:git log 查看某次本地提交内容:git show [commitId](不填则显示最新提交内容) [fileName.xx](指定提交的指定文件内容) 撤销/回滚某次提交:blog.csdn.net/qq_33442844… 分支相关操作: 切换本地已存在的分支:git checkout xxx(分支名) 删除本地分支:git branch -d xxx(分支名) 以远程分支新建本地分支并切换到此分支:git checkout -b localBranchName origin/remoteBranchName 以本地分支创建远程分支:git push origin remoteBranchName:localBranchName 设置默认追踪的远程分支: git branch -u origin/remoteBranchName(远程分支名) [localBranchName](省略则默认为当前本地分支) 用当前本地分支创建远程分支并建立追踪关系:git push -u origin xxx(新的远程分支名) 批量删除本地/远程分支:www.cnblogs.com/yoable/p/81… 隐藏改动相关操作: (1)git stash save "save message" : 执行存储时,添加备注,方便查找,只有git stash 也要可以的,但查找时不方便识别。 (2)git stash list :查看stash了哪些存储 (3)git stash show :显示做了哪些改动,默认show第一个存储,如果要显示其他存贮,后面加stash@{num},比如第二个 git stash show stash@{1} (4)git stash show -p : 显示第一个存储的改动,如果想显示其他存存储,命令:git stash show stash@{num} -p ,比如第二个:git stash show stash@{1} -p (5)git stash apply :应用某个存储,但不会把存储从存储列表中删除,默认使用第一个存储,即stash@{0},如果要使用其他个,git stash apply stash@{num} , 比如第二个:git stash apply stash@{1} (6)git stash pop :命令恢复之前缓存的工作目录,将缓存堆栈中的对应stash删除,并将对应修改应用到当前的工作目录下,默认为第一个stash,即stash@{0},如果要应用并删除其他stash,命令:git stash pop stash@{num} ,比如应用并删除第二个:git stash pop stash@{1} (7)git stash drop stash@{num} :丢弃stash@{num}存储,从列表中删除这个存储 (8)git stash clear :删除所有缓存的stash

查看分支相关操作: 查看本地分支:git branch 查看远程分支:git branch -r 查看所有分支:git branch -a 查看本地与远程分支对应关系:git branch -vv 另外,可以使用git stash命令将本地分支上工作目录(的已追踪文件)的修改与暂存区的内容转移隐藏起来,然后等合适的时机通过git stash pop将隐藏内容转移过来,隐藏的内容可在分支间共享。(使用场景是:开发中到紧急bug fix需要将工作内容保存起来,或本地分支间的代码转移等等)。

2.4 文件状态

可以用 git status 命令查看哪些文件处于什么状态,常见操作下的文件状态转换如下图所示: 基于Git的主干开发工作流 未追踪(untracked): 表示文件存在在工作目录中,但未添加到Git中去参与文件版本管理。 未修改(unmodified): 表示文件已在Git版本管理中,工作目录的文件与版本库相比没有任何改动。 已修改(modified):表示在工作目录下修改了文件;工作目录是对项目的某个版本独立提取出来的内容,放在磁盘上供你使用或修改,但是还没暂存到暂存区或提交到本地代码库中。 已暂存(staged): 表示对已修改文件的当前版本做了标记,存放在一个不可见的暂存区域(可通过git ls-files命令查看),做为下次提交的内容的一部分。其实暂存区域是一个文件,保存了下次将提交的文件列表信息,有时候也被称作索引。

2.5 文件对比

三、 Git工作流之:主干开发

基于Git的主干开发工作流 使用主干开发后,我们的远程代码库原则上就只能有一个 Master 分支了,但本地可以随意创建多个分支供自己使用(比如可以专门基于本地主要开发分支创建一个分支用来做没有把握的尝试性的实验性的开发,因为当正确的代码与尝试性的代码混合起来时要剔除这些错误的尝试性代码如同合并两个长久未合并的分支一样是非常麻烦的,所以当尝试性的开发失败时,直接丢弃这个分支是很方便的,若尝试成功,则可以在本地merge回自己的本地主要开发分支),所有新功能的提交也都提交到 Master 分支上,没有了分支的代码隔离,测试和解决冲突都变得简单,持续集成也变得稳定了许多,问题也接踵而至,主要有以下四个:

  1. 如何避免发布的时候引入未完成的 Feature
  2. 如何进行线上 Bug Fix
  3. 如何重构
  4. 如何解决冲突

3.1 如何避免发布引入未完成 Feature

答案是: Feature Toggle。 既然代码要随时保持可发布,而我们又需要只有一份代码来支持持续集成,在代码库里加一个特性开关来随时打开和关闭新特性是最容易想到的也是最容易被质疑的解决方案。 Feature Toggle 是有成本的,不管是在加 Toggle 的时候的代码设计,还是在移除 Toggle 时的人力成本和风险,都是需要和它带来的价值进行衡量的。事实上,在我们做一个前端的大特性变更的时候,我们确实没有因为没办法 Toggle 而采用了一个独立的 Feature 分支,我们认为即使为了这个分支单独做一套 Pipeline,也比在前端的各种样式间添加移除 Toggle 来得简单。但同时,团队应该约定在每次提交前都要先将 Master 分支 Merge 到 Feature 分支,以此避免分支隔离久以后合并时的痛苦。

3.2 如何进行线上 Bug Fix

在发布时打上 Release Tag,一旦发现这个版本有问题,如果这个时候Master分支上没有其他提交,可以直接在 Master 分支上 Hot Fix,如果 Master 分支已经有了提交就要做以下几件事:

从 Release Tag 创建发布分支。 在 Master 上做 Fix Bug 提交。 将 Fix Bug 提交 Cherry Pick 到 Release 分支。 在Release 分支再做一次发布。

之所以不在 Release 分支直接 Fix,再合并到 Master 分支这样做,是为了防止bug fix后忘记merge回Master分支。

3.3 如何重构

这里指的是比较大规模的重构,无法在一次提交完成,TBD 要求每一次提交都是一个可上线的版本,所以这同时还意味着这个重构无法再一个上线周期内完成。 这种情况,需要在代码设计中增加一个抽象层,保证在重构过程中先不动原来的代码,也不破坏既有功能,类似于蓝绿部署中的负载均衡器的作用,这样的流程就是: 在将要被重构的代码逻辑附近引入抽象层然后提交,对所有人可见。如果有需要可以是多个提交,这些提交都不能破坏 build这些提交都不能破坏 build,然后依次 push 到共享代码库。 为将要被引入的代码写抽象层的第二次实现,然后提交。但在主干上由于关闭状态所以其他开发人员暂时不依赖于它。如果需要的话,这可能像上面那样需要多次提交。第一步的抽象层也可能偶然被调整,但必须遵循同样的原则: 不能破坏build。 切换使用重构后的代码,然后 Push。 删除原有的旧实现(被重构代码) 删除抽象层 主干开发参考文章连接: 1.主干开发 2.功能开关 3.功能开关 4.分支模型与主干开发

四、GIT中常见场景的问题解决方法

4.1 变基的风险

如果你只对不会离开你电脑的提交执行变基,那就不会有事。 如果你对已经推送过的提交执行变基,但别人没有基于它的提交,那么也不会有事。 如果你对已经推送至共用仓库的提交上执行变基命令,并因此丢失了一些别人的开发所基于的提交, 那你就有大麻烦了,你的同事也会因此鄙视你。 如果你或你的同事在某些情形下决意要这么做,请一定要通知每个人执行 git pull --rebase 命令,这样尽管不能避免伤痛,但能有所缓解。总的原则是,只对尚未推送或分享给别人的本地修改执行变基操作清理历史, 从不对已推送至别处的提交执行变基操作,这样,你才能享受到两种方式带来的便利。

4.2 撤销错误的合并

合并提交并无不同。 假设现在在一个主题分支上工作,不小心将其合并到 master 中,现在提交历史看起来是这样: 基于Git的主干开发工作流 有两种方法来解决这个问题,这取决于你想要的结果是什么。

方法1:修复引用

如果这个不想要的合并提交只存在于你的本地仓库中,最简单且最好的解决方案是移动分支到你想要它指向的地方。 大多数情况下,如果你在错误的 git merge 后运行 git reset --hard HEAD~,这会重置分支指向所以它们看起来像这样: 基于Git的主干开发工作流 reset --hard 通常会经历三步:

1.移动 HEAD 指向的分支。 在本例中,我们想要移动 master 到合并提交(C6)之前所在的位置。 2.使索引看起来像 HEAD。 3.使工作目录看起来像索引。

这个方法的缺点是它会重写历史,在一个共享的仓库中这会造成问题的。 查阅 变基的风险 来了解更多可能发生的事情; 用简单的话说就是如果其他人已经有你将要重写的提交,你应当避免使用 reset。 如果有任何其他提交在合并之后创建了,那么这个方法也会无效;移动引用实际上会丢失那些改动。

方法2:还原提交

如果移动分支指针并不适合你,Git 给你一个生成一个新提交的选项,提交将会撤消一个已存在提交的所有修改。 Git 称这个操作为“还原”,你可以这样调用它: git revert -m 1 HEAD -m 1 标记指出 “mainline” 需要被保留下来的父结点。 当你引入一个合并到 HEAD(git merge topic),新提交有两个父结点:第一个是 HEAD(C6),第二个是将要合并入分支的最新提交(C4)。 在本例中,我们想要撤消所有由父结点 #2(C4)合并引入的修改,同时保留从父结点 #1(C6)开始的所有内容。

有还原提交的历史看起来像这样: 基于Git的主干开发工作流 新的提交 ^M 与 C6 有完全一样的内容,所以从这儿开始就像合并从未发生过,除了“现在还没合并”的提交依然在 HEAD 的历史中。 如果你尝试再次合并 topic 到 master Git 会感到困惑: $ git merge topic Already up-to-date. topic 中并没有东西不能从 master 中追踪到达。 更糟的是,如果你在 topic 中增加工作然后再次合并,Git 只会引入被还原的合并 之后 的修改。 基于Git的主干开发工作流 解决这个最好的方式是撤消还原原始的合并,因为现在你想要引入被还原出去的修改,然后 创建一个新的合并提交: $ git revert ^ [master 09f0126] Revert "Revert "Merge branch 'topic'"" $ git merge topic 基于Git的主干开发工作流 在本例中,M 与 ^M 抵消了。 ^^M 事实上合并入了 C3 与 C4 的修改,C8 合并了 C7 的修改,所以现在 topic 已经完全被合并了。

转载自:https://juejin.cn/post/6844903969521664008
评论
请登录