美文网首页
Git工作流基础——中心化的工作流

Git工作流基础——中心化的工作流

作者: Rollo_Tomasi | 来源:发表于2022-03-22 14:25 被阅读0次

    中心化的工作流对于刚从SVN切换到GIT的团队来说是一种非常合适的工作流。就像SVN一样,中心化的工作流会使用中心仓库作为项目更改的唯一入口。当然不同于将中心分支命名为trunk,默认的开发分支在Git中被称为main,所有的修改会被提交到这个分支中。这一工作流除了main以外,就不再需要其他分支了。

    虽然从一种版本管理系统切换到另外一种看上去是一件令人生畏的任务,但迁移到Git上并不需要对之前的工作流做什么改变。团队成员的协作方式仍然可以一如既往。

    然而仍然需要说明,相比于SVN,在开发流程中使用Git会自动赋予开发团队一些优势。首先,Git能够让每个团队成员在本地保留一份完整的项目拷贝。由于这种隔离的开发环境,使得每个开发人员都可以独立于整个项目中所有其他变更进行自己的开发 ——也就是说,他们可以在自己的本地拷贝中提交新的变更,完全不需要理会上游的变更,直到开发人员认为自己的开发工作已经准备就绪。

    其次,可以使用Git健壮的分支合并模型。与SVN不同,Git的分支模型设计为一种故障安全的机制,因此对于集成代码以及共享协作的流程来说,开发人员在很大程度上会减少心理负担。在利用远程服务端托管项目仓库,以便开发者进行pull和push操作的角度来说,中心化工作流与其他工作流没有什么太大差别。但与其他工作流相比,中心化工作流没有明确的pull request或者fork模式的定义。总体来说中心化工作流更适合与刚从SVN迁移到Git的团队或者是小型团队。

    工作方式

    开发者的工作始于从中心仓库将整个仓库克隆到本地。在本地项目中,进行编辑,然后提交变更,这一切都和使用SVN一致;只不过,这些提交到此为止都只是存储在本地——正如前面所述,这些操作都是与中心仓库隔离的。这样可以让开发者延迟与上游的同步操作,直到他们觉得需要进行同步了。

    向中心仓库发布自己的变更时,开发者执行push命令将本地的main分支推向中心仓库。这一操作与svn commit相当,不同的是push会将所有远程仓库中没有的变更一次性推向远程。

    初始化中心仓库

    01 Central Repository.png

    在一切发生之前,得有个人在远程服务器上创建中心仓库。如果是一个全新的项目,可以初始化一个空白的仓库。否则需要导入已经存在的Git或者SVN仓库。

    中心仓库应该总是一个裸仓库(不包含工作目录的仓库),可以通过下面的命令进行创建:

    ssh user@host git init --bare /path/to/repo.git
    

    请使用有效的SSH username替换 user,用有效的IP或者域名替换host,并且将 /path/to/repo.git替换为你希望的远程服务器路径。注意路径末尾的.git扩展名通常会保留在最后以表明远程仓库是一个裸仓库。

    托管的中心仓库

    大多数情况下,我们已经不需要自己维护git server,而是通过第三方服务进行托管,让他们来为我们创建中心仓库,比如使用 gitlab 或者github。在这种情况下第三方服务会帮你处理初始化一个裸仓库的过程。创建之后托管服务商会提供一个中心仓库的地址,以便可以在本地使用中心仓库。

    clone中心仓库

    接下来,开发者在本地创建整个项目的拷贝。使用git clone命令来进行:

    git clone ssh://user@host/path/to/repo.git
    

    在对仓库执行clone操作的同时,Git会自动为远程仓库添加一个叫做origin别名,毕竟未来你还会不断与远程仓库进行交互。

    编辑和提交变更

    一旦完成本地拷贝的克隆,开发者即可使用标准Git提交流程对仓库作出一些变更:编辑,暂存,提交。也许你还不太了解暂存的概念,这是一种用来准备仅提交一部分变更的方式,通过这种方式不需要把本地所有变更都进行提交。这一能力允许开发者每次提交都保持高度的聚焦,即便此时的本地仓库已经包含一大堆的变更,也仍然可以仅进行一小部分代码的提交操作。

    git status # View the state of the repo
    git add <some-file> # Stage a file
    git commit # Commit a file</some-file>
    

    请注意由于以上命令仅在本地创建提交记录,张三可以不断重复这些操作而不用担心会对中心仓库产生什么影响。当你面对一个大功能开发,需要对此大功能分解为几个小的步骤时,这一特性会显得非常有意义。

    向中心仓库推送提交

    一旦对本地仓库提交了新的变更,为了与其他开发者共享这些新的变更,我们需要把它们推向远程仓库。

     git push origin main
    

    这个命令会把本地的新提交推送到中心仓库。当向中心仓库推送变更时,会有可能由于其他开发者在之前已经推送了与本次推送的更新产生冲突的代码段落。这时Git会输出一些信息表明推送产生了冲突。在这种情况下,需要先执行git pull命令。我们将会在下面的段落中深入这里所提及的冲突的场景。

    应对冲突

    中心仓库代表着官方项目代码,所以中心仓库的提交历史应当神圣不可侵犯。如果开发者本地的提交历史与中心仓库的提交历史产生分歧,Git会拒绝将变更推送到中心仓库,因为会覆盖中心仓库的提交记录。

    02 Local Repository.png

    对于开发者来说正确的做法是,在推送变更之前,先从中心仓库更新远程的提交历史记录,然后将本地的变更rebase到远程提交记录的顶端。这就好比是说:让我的变更建立在所有其他人的工作的基础之上。操作的结果会显示为一条完美的线性提交历史。

    如果本地变更与上游提交内容产生了冲突,Git会暂停rebase过程,让你手动解决这些冲突。与发起提交时需要使用的git statusgit add一样,在解决冲突时也是使用相同的命令。这一统一的行为让新手也能够比较轻松的应对好合并过程中的冲突。另外,如果在合并过程中发生了大麻烦,Git也提供了简单的命令直接退出rebase过程,可以再重新来过,或者去找别人帮忙。

    举例

    让我们通过一个具体的例子来看看一个典型的小型团队如何使用中心化的工作流开展协作。在示例中有两名开发者,李雷和韩梅梅,他们俩独立开发各自的功能,然后通过中心仓库分享各自的工作成果。

    李雷和他的功能

    06 (2).png

    在他的本地仓库中,李雷按照标准Git提交流程开发自己的功能:编辑,暂存,提交。

    记住这些操作都是在本地发生,李雷可以无数遍的操作而无需担心会影响到中心仓库。

    韩梅梅和她的功能

    07.png

    与此同时,韩梅梅也在她自己的本地环境中开发自己的功能,同样通过:编辑,暂存,提交的流程。跟李雷一样,韩梅梅也不用担心自己的所作所为会对中心仓库造成什么影响。并且她也一点不关心李雷在做些什么,因为所有的本地仓库也都是各自独立的。

    李雷发布他的功能

    08.png

    当李雷完成功能开发,他应当把本地提交发布到中心仓库,这样团队的其他成员就可以使用这个新功能。他可以像下面这样使用git push命令:

     git push origin main
    

    还记得origin是李雷当初clone中心仓库内容时,Git自动创建的用于指向中心仓库的别名吗?main参数告诉Git这次发布是将本地的main分支的变更推向中心仓库的main分支。由于此时此刻距离李雷首次clone中心仓库的内容这段时间内,中心仓库还没有任何的更新,所以这次push操作不会造成什么冲突,发布结果会如期而至。

    韩梅梅尝试发布新功能

    09.png
    让我们来看看如果韩梅梅尝试在李雷推送完成之后,推送自己的新功能时会发生什么。首先,韩梅梅会使用相同的命令进行push操作:
     git push origin main
    

    但是由于她本地提交历史已经与中心仓库的提交历史产生了不一致,Git会拒绝这次推送,并输出一些错误信息

    error: failed to push some refs to '/path/to/repo.git'
    hint: Updates were rejected because the tip of your current branch is behind
    hint: its remote counterpart. Merge the remote changes (e.g. 'git pull')
    hint: before pushing again.
    hint: See the 'Note about fast-forwards' in 'git push --help' for details.
    

    这样韩梅梅就不能直接推送本地更新,也阻止了韩梅梅的提交历史覆盖中心仓库的提交历史。此时韩梅梅需要先拉取李雷的更新内容,然后将李雷的更新内容整合进自己的提交历史,再进行push操作。

    韩梅梅rebase到李雷的提交历史顶部

    10.png
    韩梅梅可以使用git pull把上游的变更融合进本地仓库中的本地提交。
     git pull --rebase origin main
    

    --rebase选项告诉Git把所有韩梅梅的本地提交放到main分支的顶端,在与中心仓库进行同步之后,main分支的顶端也就是远程仓库的提交历史的顶部,如下图所示:

    03 Mary's Repository.png
    如果你没有添加--rebase选项,仅仅使用默认的pull命令也可以。但是这会在提交历史中留下一个多余的合并提交记录。在中心化的工作流中,最好是使用rebase方式而不是生成一个合并提交,这样会让提交历史看起来更加清爽。

    韩梅梅解决合并冲突

    12.png
    rebase的工作流程是,将本地的commit转移到更新后的main分支顶部,对于本地的多个commit会多次重复执行这一过程。这意味着在rebase过程中需要一次一次地解决每一个commit相对于main分支的局部冲突,而不是像合并提交一样,一次解决一大堆commit整合在一起之后相对于main分支的整体冲突。这过程会保持每个提交更加专注,并且让项目的提交历史更清晰。反过来说,这也能够让我们更容易发现bug是在哪次提交引入的,如果必须要回退,可以回退特定commit把对于整个项目的影响降到最低。

    如果韩梅梅和李雷分别开发不相关的功能,rebase过程中通常不会产生冲突。但是如果有冲突发生,Git会暂停在rebase当前commit的操作步骤过程中,并输出下面的信息,其中包含解决冲突所需要的相关信息:

    CONFLICT (content): Merge conflict in <some-file>
    
    04 Mary's Repository.png
    此时韩梅梅执行git status命令来查看到底哪些文件产生了冲突。命令的输出内容会标注出哪些文件没有得到自动合并,并产生了冲突:
    # Unmerged paths:
    # (use "git reset HEAD <some-file>..." to unstage)
    # (use "git add/rm <some-file>..." as appropriate to mark resolution)
    #
    # both modified: <some-file>
    

    然后根据上面的输出,韩梅梅按照自己的想法编辑产生冲突的文件。处理完成之后她就可以将文件暂存,然后执行git rebase --continue以便让rebase过程继续进行。

    git add <some-file>
    git rebase --continue
    

    这就是所有需要的操作了。Git会继续将新的commit转移到main分支的顶部,不断重复这一过程,直到某个其他commit产生冲突时再暂停rebase。

    如果在rebase某个commit的过程中,冲突内容让你觉得手足无措了,也不要紧张。执行下面的命令会退出rebase过程,并回到整个rebase开始之前的状态

    git rebase --abort
    

    韩梅梅成功发布了新功能

    14.png

    到此为止韩梅梅已经完成了本地仓库与中心仓库的同步工作,可以将本地的新功能推送到中心仓库去了:

     git push origin main
    

    还有什么

    中心化工作流对于小团队来说相当不错。但是当团队规模成长起来之后,上面示例中所使用的冲突解决流程将成为团队协作的瓶颈。如果你的团队觉得中心化工作流使用起来很舒适,同时又想获得更简洁的协作方式,那么应该尝试了解一下功能分支工作流。通过引入一个对应单独功能的独立分支,足以让独立功能在整合进主分支之前获得充分的检查和调整空间。

    其他常见工作流

    第一个介绍看上去过于老旧的中心化工作流,其实是因为其他所有工作流程都是基于这个最简单的工作流之上。大部分流行的Git工作流都会包含一个某种程度上中心化的仓库,以及个人开发者与其进行pull和push之类的交互。下面我们会对其他一些常见Git工作流做一些简单的介绍。这些扩展后的工作流在诸如多功能开发,hotfixes,以及发布版本管理等场景下的分支管理提供了特定的解决模式。

    功能分支工作流

    功能分支对于中心化工作流的扩展似乎是非常自然而合乎逻辑的。功能分支工作流背后的核心思想就是:所有功能的开发应该发生于一个专有的分支,而不是在主分支之内。这一封装可以让多个开发者同时协作开发某一个功能,而不会对主代码库产生任何影响。这也意味着main分支永远不会含有任何为开发完成的代码,这在持续集成环境中是一个不可或缺的优势。

    Gitflow 工作流

    Gitflow工作流围绕项目的发布流程定义了严格的分支模型。这一工作流没有在功能分支工作流之上引入任何新的概念或者命令。它只是定义了不同的分支如何承担不同的角色,以及何时何地在不同角色的分支之间进行交互。

    Forking工作流

    Forking工作流与本文中介绍的工作流在本质上存在不同。相对于中心化工作流中使用一个单独的服务端仓库作为中心的代码库,forking工作流允许让每一个开发者拥有一个属于自己的服务端仓库。这意味着对于每个贡献者来说,拥有的不仅仅是一个,而不是两个仓库:一个私有的本地仓库,一个公共的服务端仓库。

    Guidelines

    指导建议

    没有一种Git工作流可以适合所有团队。如前所述,对于自己的团队来说,应该发展出一个适合自己团队提升产出的工作流程。除了团队文化的适应度以外,工作流还应该适应团队的业务文化。比如说Git的分支和tag管理应该适用于你的业务发布周期。如果你还在使用一些项目管理工具来追踪项目周期,你也许还希望定义与任务开发进程对应的分支。此外,下面也列了一些选择工作流时需要考虑的因素:

    短期分支

    一个分支存在的时间越长,也就意味着它与主分支隔离的时间越长,也进一步意味着在合并时产生冲突的概率越大。尽量让分支的生命周期短一些可以让合并过程更顺畅,合并结果更干净。

    细粒度的回退

    一个工作流规范的重要价值在于可以主动防范回退的发生。在进行分支合并之前就对分支进行测试是一个例子。然而通常情况下仍然会有其他原因导致的回退需要发生。这种情况下,一套好的工作流程应该能够让回退流程变得简单,而且尽量降低对其他团队成员的影响。

    匹配发布周期

    工作流应该适应你的业务发布周期。如果你的业务周期要求一天发布若干次,那么你需要一种工作流可以保证主分支永远是稳定的。如果你的业务发布周期没有那么频繁,你可能可以考虑使用Git的标签功能,来为一个待发布分支打上对应的版本号标签。

    总结

    在本文档中我们讨论了Git工作流。过程中我们深入检视了中心化工作流在实际应用中的示例。之后又扩展到基于中心化工作流的其他工作流模式。最后我们希望在本文中得出以下结论:

    • 没有绝对普适的Git工作流
    • 工作流应该尽量简单,且能够提升团队产出
    • 业务需求应该能帮助你塑造适合的Git工作流

    相关文章

      网友评论

          本文标题:Git工作流基础——中心化的工作流

          本文链接:https://www.haomeiwen.com/subject/gdyfjrtx.html