• Git 使用基础指南


    0. 参考文档

    本文中绝大部分内容来自以下两个文档,想要深入了解某一个主题的,建议仔细查看原文档。

    1. 基本概念

    1.1 文件状态

    • 已修改(modified):对应工作区(Working directory),包括新增和修改的文件;
    • 已暂存(staged):对应暂存区(Staging area);
    • 已提交(committed):对应提交历史(Git directory)。

    简单理解其相互关系:

    已修改 - add -> 已暂存 - commit -> 已提交 - checkout -> 已修改

    1.2 提交与分支

    提交(Commit)和分支(Branch)的关系,可以类比链表指针。Git 中的分支,本质上是个指向 commit 对象的可变指针。

    如下图所示,提交历史共有 3 次提交记录,每一次提交可以类比为链表中的一个节点,节点包含指向下一个节点(上一次提交)的指针信息。

    另外还有三个特殊的指针,mastertesting是两个分支,可以类比为两个指向链表头的指针;HEAD是一个特殊的指针,用于指示当前工作区所在的位置。

    如下图所示,是在testing分支上工作后,进行了一次提交。master仍然指向原来的提交,testing指向最新的提交,而由于当前在testing分支上工作,HEAD随着 testing 一起向前移动了一步。

    如下图所示,如果切换到master分支上,并且又进行了一次提交,则masterHEAD一起向前移动,指向87ab2这次提交。

    综上所属,分支是一个指针,而不是一个容器

    在 Git 的概念里,分支是一个很轻量化的概念,新建、修改和移除分支的代价都不大。因此也推荐保持分支的短期性,一个分支专注于一项特定的任务,即来即走。

    1.3 合并(merge)

    合并操作的对象都是分支,包括:

    • 源分支:包含新增内容的分支;
    • 目标分支:需要并入新增内容,会发生改变的分支。

    合并有两种形式:

    • 快速向前合并(Fast-forward merge)
    • 三方合并(Three-way merge)

    1.3.1 快速向前合并

    当目标分支是源分支的直接上游时,合并时会采用快速向前的方式,并出现“Fast forward”提示。

    操作实质:把目标分支的指针直接向前移动到源分支的指针所指向的位置。

    如下图所示,包含masterhotfixiss53三个分支,由于当前masterhotfix的直接上游,将采用 Fast-forward 的合并方式。

    合并结果如下图所示,合并之后masterhotfix分支指向同一个提交。此时hotfix分支已经完成历史使命,可以删除了。

    1.3.2 三方合并

    然后回到iss53分支进行开发工作,又进行了一次提交后,需要将改动内容合并入master,而此时,这两个分支已经分岔了。Git 会用两个分支的末端(C4 和 C5)以及它们的共同祖先(C2)进行一次三方合并计算。

    Git 对三方合并后的结果重新做一个新的快照,并自动创建一个指向它的提交对象(C6),如下图所示。此时iss53分支已经完成历史使命,也可以删除了。

    1.3.3 冲突解决

    并不是每一次合并操作都非常顺利,有时可能会产生冲突。

    <重要原则>:快速向前合并从原理上就不会产生冲突,冲突只会发生在三方合并的时候。

    冲突产生的原因:简单理解,可以认为是 C4 的提交,和 C3-C5 的提交,修改了文件的同一个部分。Git 无法自动合并,必须由人来裁决。

    冲突解决的步骤:解决冲突的步骤与进行一次新的提交的步骤几乎一样。(1)修改冲突的代码,(2)将修改过的文件加入缓冲区(add 操作),(3)进行一次合并提交(commit 操作)。

    1.4 衍合(rebase)

    1.4.1 基本原理

    衍合是除了合并操作以外,另一种把一个分支中的修改整合到另一个分支中的办法。

    回到两个分支进行了各自的提交而导致分岔的情况下,如下图所示。

    现象这样的场景:

    当你在experiment分支上工作时,有其他人向master分支上推送了新的内容。

    你想要把master中的更新同步到experiment分支中,如果使用 merge 操作会产生一个额外的合并提交。

    而 rebase 的操作逻辑是:把在 C3 里产生的变化补丁在 C4 的基础上重新打一遍。操作结果如下图所示。

    此时,你的experiment分支包含了master分支上的最新内容,可以以此为基础继续开发。(默认master分支上的内容都是稳定的,需要所有人向其兼容的)。

    如果需要把experiment分支上的内容也并入master分支,可以执行安全的快速向前合并。结果如下图所示。

    1.4.2 重要守则

    git rebase是 Git 操作中的黑魔法,用好了可以化腐朽为神奇,用不好会带来灾难性后果。

    <重要原则>:绝不要在公共的分支上使用它!!!

    用更白话一点的说法:从分岔点开始往后的提交,如果已经 push 过,那就已经是公共的提交了,这个分支就是公共分支,必须假设其他人的工作会依赖于这些公共提交,也就不能再用 rebase 操作了。

    因为衍合的过程改变了分支的历史,原来的 C3 变成了 C3',如果之前 C3 已经发布到了远程,则在本地变更为 C3'后,远程分支与本地分支不一致了,会导致后续的 push 操作无法进行。

    因为在进行 push 操作的时候,将本地分支推送到关联的远程分支时,本质上也是一个 Fast-forward 模式的合并操作。

    1.5 远程服务器

    虽然只在本地工作也能使用 Git,但为了协作,一般会有一个代码托管的远程服务器,运行一个 Git 服务。

    1.5.1 远程分支和本地分支

    本质上,每一个 Git 库的副本,都有自己的一套分支,而不同副本之间的分支可以进行关联追踪。

    在一个本地库上执行git branch -a,典型会有有如下显示(部分删节):

      develop
    * feature_xxx
      master
      remotes/origin/HEAD -> origin/master
      remotes/origin/develop
      remotes/origin/feature_xxx
      remotes/origin/master
    

    前三个是本地分支,带remotes前缀的是远程分支,origin代表服务器名。

    执行git branch -vv查看分支的追踪关系,有如下显示(部分删节):

      develop                  1bf6d01 [origin/develop]
    * feature_xxx              7f3d5c9 [origin/feature_xxx]
      master                   40f9f56 [origin/master]
    

    一些有效但不那么准确的理解:

    • 远程分支和本地分支是不同的分支;
    • 具有追踪关系的远程/本地分支之间,通过 pull/push 操作进行同步,合并方式限定为 Fast-forward。

    2. Git 工作流

    使用一个简化的Git Flow工作流。

    2.1 Git 分支类型

    将会使用masterdevelopfeature三种分支,暂时不使用hotfixrelease分支。

    • master分支:
      • 仓库的主分支,包含最近发布的可稳定使用的版本;
      • 一般由仓库管理员从develop分支进行合并,不能直接向master进行推送;
      • master分支始终存在,不可删除。
      • master的每一个 commit 都应该打上标签,作为对外发布的版本号。
    • develop分支:
      • 主要开发分支,基于master创建,始终包含最新完成功能的代码以及 bug 修复后的代码;
      • 接受从feature发起的合并请求,不能直接向develop进行推送;
      • develop分支始终存在,不可删除。
    • feature分支:
      • 基于develop创建,用于某一个特定的新功能/新特性开发;
      • feature分支可同时存在很多个,用于多个功能同时开发;
      • feature分支属于临时分支,当合并入develop后,建议选择删除,如有继续开发的需要,重新基于develop创建新的feature分支。
    • develop基于master创建,并且在develop向前演进的过程中,master不会接受其他来源的提交和合并。因此,每一次developmaster分支的合并,都应该是 Fast-forward 模式的。
    • feature基于develop创建,并最终合并入develop,在此过程中,develop可能会合并入其他feature分支的内容,如何保证提交 PR 时不产生合并冲突,在下一节详细讨论。
    • 以文档内容为主的 WIKI 库不设develop分支,围绕master分支进行协作。

    2.2 基本原则

    几项 Git 协作的基本原则:

    • 重要:在远程服务器上,只会进行 Fast-forward 模式的合并,并且是通过 Pull-Request 进行。在提交 PR 之前,需要确认目标分支(一般是develop)是源分支(一般是feature)的直接上游。可以通过提交图进行确认。

    • 重要:所有的冲突都在本地解决,在远程没有解决冲突的途径。

    • 保持每一次 Commit 有明确的意义,必要时通过交互式的 rebase 操作,对 Commit 进行整理。

      整理方法参见说明

    • 保证分支命名规范且有意义,保证 commit-message 包含简明且充分的信息。

      branch-name 和 commit-message 的规范参见协作规范文档

    • 保持合适的推送频率(Push 操作,非 Commit 操作)

      针对正在执行的任务,如果 3 天以上都没有 Push,无法让其他人同步进度,则说明提交频率过低;如果在一个时段内连续提交,疯狂刷屏,则说明推送频率过高。

      建议针对某一个具体的开发任务,一天推送 1 至 2 次是一个合适的频率;可以在每天工作结束前,整理当天的 Commit,并执行一次 Push 操作。

    2.3 典型工作流程

    2.3.1 仓库初始化

    从 Server 端通过git clone获取仓库一个完整的副本,并在本地新建feature_0分支。

    2.3.2 合并流程

    本地feature_0分支有了两个新的提交(C3&C4),在此同时,远程的origin/develop接受了feature_1分支的 PR,向前推进到了 C5。如下图所示:

    不合理的做法

    此时,如果直接将本地的feature_0分支推送到远程,则origin/developorigin/feature_0处于分岔的状态。如下图所示:

    按照一般 PR 的规则,无法将origin/feature_0合并入origin/develop,因为目标分支并不是源分支的直接上游。如果强行合并,若刚好两个分支没有修改过同一个文件,则可以侥幸合并成功。

    但并不推荐这么做,因为违反了 PR 只做 Fast-forward 操作的原则。且很多时候会产生冲突,在网页上无法处理。

    如下图所示:

    合理的做法

    在推送feature_0分支之前,先将本地的develop分支和远程的origin/develop进行同步(通过git pull操作)。如下图所示:

    在本地进行衍合操作,将feature_0分支的提交,更新到develop分支之前。

    # 在 feature_0 分支下
    git rebase develop
    

    如果发生冲突,则按照命令行的提示手动解决冲突。正确 rebase 后的结果,如下图所示:

    在此基础上,本地分支继续开发,又向前推进了一次提交,然后同步到远程的origin/feature_0分支上。此时,origin/developorigin/feature_0的直接上游,可以实现 Fast-forward 模式的合并。(假设在此期间origin/develop没有再接受新的 PR,如有,则重复上面的流程)。结果如下图所示:

    2.3.3 衍合的注意事项

    回到最初分岔的阶段,如下图所示:

    假设已经将feature_0分支推送到远程,与origin/feature_0同步过一次。

    若此时想基于origin/develop最新提交的基础,进行继续开发,先将更新拉取到本地,如下图所示:

    在本地执行之前一样的衍合操作,将feature_0分支添加到develop分支的头部,会发现本地的feature_0和远程的origin/feature_0不一致了,此时无法再执行 Push 操作,因为本地和远程发生了冲突。如下图所示:

    如果已经将提交推送至远程,有两种后续的解决方案:

    (1)通过 merge 而非 rebase 进行合并,在本地执行三方合并

    同时要求在本地解决可能的合并冲突。结果如下图所示:

    此时将feature_0推送到远程,与origin/feature_0进行同步。这时,origin/developorigin/feature_0的直接上游,符合 PR 的规则,可以正常提交合并请求。结果如下图所示:

    (2)如果本地已经使用了 rebase 操作进行合并,可以强制推送进行同步

    强制推送git push -f是另一个比较危险的操作。

    <重要原则>:一定只在自己的工作分支上使用!!!一定不能向其他人的工作分支执行强制推送!!!

    除非万不得已,不要使用强制推送。在推送前,需要反复确认本地分支包含了所有需要的工作内容。

    推送后,远程分支和本地分支已经一致,并且origin/developorigin/feature_0的直接上游。结果如下图所示:

    2.3.4 再谈合并与衍合

    • git rebasegit merge 做的事其实是一样的,它们都被设计来将一个分支的更改并入另一个分支,只是实现方式不同,最终的文件结果是一致的。

    • git merge的好处是,不会对历史进行任何更改,是安全的;缺点是,每次合并都会引入一个额外的提交(比如上图的 C6),如果上游分支非常活跃,一定程度会污染本地的开发分支,形成一个非常复杂的开发历史。

    • git rebase的好处是,开发历史会非常整洁和线性,没有不必要的合并提交;缺点是,这个操作包含一定的风险性,会变更开发历史,所以必须严格遵守衍合的操作守则——绝不要在公共的分支上使用它!!!任何衍合操作都不应该更改已经同步到远程服务器的提交。

    • git rebase的一个额外的好处是,可以用来清理提交历史,让历史中的每一次提交都包含明确的开发意义,便于回顾和追溯。

      这意味着,在开发过程中,本地可以进行相对随意的 Commit,在整体 Push 之前,通过git rebase -i对开发历史进行整理,合并部分 Commit,修改 commit-message。

    2.3.5 推荐的操作方法

    综上所述,一个推荐的基于 git 协作的开发流程:

    1. 将本地的develop分支与远程的origin/develop进行同步,基于最新的develop,新建本地的开发分支feature
    2. feature分支上工作,正常进行git commit操作;
    3. (可选)开发过程中关注origin/develop的更新,如果更新的内容与feature开发的内容相关,则拉取到本地,通过git rebase操作,合并入feature
    4. 在执行git push前,如有必要,通过git rebase -i对提交历史进行整理,注意只清理还未推送到远程的提交;
    5. 在执行git push前,必须确认origin/develop的状态,执行一次git pull操作,如果有更新,则拉取到本地,通过git rebase操作,合并入feature
    6. 确认feature在合并origin/develop的最新更新后,依然能够按照预想的方式正常运行,如果有问题则修正;
    7. 执行git push操作,将feature推送到远程,与origin/feature进行同步。

    3. Git 配置

    在使用 Git 前,对其进行有效的配置,可以达到事半功倍的效果。

    3.1 配置层级

    Git 的配置包含三个层级,每一级别的配置会覆盖上层的相同配置:

    • 系统配置/etc/gitconfig文件,设置时配合--system选项;
    • 用户配置~/.gitconfig文件,设置时配合--global选项;
    • 项目配置./git/config文件。

    3.2 必须配置

    设置名字和邮箱,用于识别用户,会记录到每一次 commit 的信息中。

    git config --global user.name "John Doe"
    git config --global user.email johndoe@example.com
    

    3.3 建议配置

    3.3.1 文本编辑器

    git config --global core.editor vim
    

    建议用 vim,如果不习惯可以用 gedit、code 之类代替。

    3.3.2 commit-message 模板

    使用 commit-message 模板可以减少每次 commit 操作时需要固定输入的内容。

    设置模板文件
    $HOME/.gitmessage.txt中或新建任意文本文件,写入如下内容:

    <feat>(XXX):
    <>
    

    启用模板文件

    git config --global commit.template $HOME/.gitmessage.txt
    

    3.3.3 自动换行符

    Windows 使用回车和换行两个字符来结束一行(CRLF),而 Mac 和 Linux 只使用换行一个字符(LF)。

    # 只在window开发,linux/windows运行
    $ git config --global core.autocrlf true
    # linux开发和运行
    $ git config --global core.autocrlf input
    # 只在windows开发和运行
    $ git config --global core.autocrlf false
    

    4. 建议

    使用命令行操作,减少对 IDE 内置工具的依赖。

    • 掌握命令行操作,能够让你更好地理解 Git 的工作原理;
    • IDE 简化了操作,但也增加了误操作的可能。
  • 相关阅读:
    private static final long serialVersionUID = 1L;
    HashMap和Hashtable的区别
    MySQL中tinytext、text、mediumtext和longtext详解
    char、varchar、text和nchar、nvarchar、ntext的区别
    java获取文件夹下文件名
    java删除文件
    js判断浏览器
    nodejs调试:node-inspector
    [转]各种开源协议介绍 BSD、Apache Licence、GPL V2 、GPL V3 、LGPL、MIT
    [IBM]掌握Ajax,Ajax中的高级请求和响应
  • 原文地址:https://www.cnblogs.com/lylec/p/15705132.html
Copyright © 2020-2023  润新知