zoukankan      html  css  js  c++  java
  • 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 简化了操作,但也增加了误操作的可能。
  • 相关阅读:
    USACO 3.3 A Game
    USACO 3.3 Camelot
    USACO 3.3 Shopping Offers
    USACO 3.3 TEXT Eulerian Tour中的Cows on Parade一点理解
    USACO 3.3 Riding the Fences
    USACO 3.2 Magic Squares
    USACO 3.2 Stringsobits
    USACO 3.2 Factorials
    USACO 3.2 Contact
    USACO 3.1 Humble Numbers
  • 原文地址:https://www.cnblogs.com/lylec/p/15705132.html
Copyright © 2011-2022 走看看