.. _branch: 分支:branch ================= 总是在一个分支上工作不是正确的 git 使用方式。通常,我们在研发分支工作,频繁地进行推送;每当累计更新到一个较稳定的版本,我们才会向 master 分支合并。还记得每个 commit 都是快照吗?事实上,每个 commit 也有一个指向本分支前一次 commit 的指针,而 git 通过一个名为 “HEAD” 的指针,来标记当前处于哪个分支。 分支的特性有多重要呢?我认为, **不了解分支,不足以谈用过 git。** 这也是将分支单独作为一章的原因。 需要指出:“master” 并不是一个特殊的分支;只不过 `git init` 命令会自动创建一个名为 master 的分支作为初始分支,而大多数用户都懒得去改动它。 创建分支 --------------- 创建一个名为 dev 的分支(我通常将开发分支叫这个名字): .. code-block:: sh $ git branch dev git 通过 HEAD 指针管理“当前分支”。我们以 `log` 命令的返回信息来说明什么是当前分支: .. code-block:: sh $ git log --oneline b895843 (HEAD -> dev, origin/dev) GitLearning: Update to 'tag' section. def0a06 Git: Init. 36e8d6b Update README. bae6fc8 (origin/master, master) Init 以上输出结果说明 HEAD 指向 dev 分支,而 master 分支停留在之前的位置。上面提到的 `orign/...` 分支是指被推送到了远程 `origin` 仓库的分支。 切换分支:checkout ------------------------- 切换到一个已有的分支: .. code-block:: sh $ git checkout dev 要新建一个分支并切换过去,添加 `-b` 选项: .. code-block:: sh $ git checkout -b test 合并分支:merge ------------------- .. important:: 合并分支是 Git 使用的重中之重。 我们通过一个例子来了解分支合并。这个例子来自与官方手册,有改动。 假设你拥有一个仓库,master 分支有 3 个提交,而你正在 dev 分支上工作: .. image:: pics/branch-01.svg :width: 80% :align: center 注意到你的 dev 分支此时是领先于 master 分支的。这时,你接到一个 issue 17,说你的 master 分支有一个问题需要立刻修复,因此你不得不切换分支去解决它。你的做法是回到 master 分支,新建一个 hotfix 分支(假设你的 dev 工作目录的改动都已经提交;我们稍晚再来讨论存在文件未提交的情况): .. code-block:: sh $ git checkout master $ git checkout -b hotfix ... (在 hotfix 分支修复了问题) $ git commit -a -m "Fix issue #17." 此时分叉(diverge)就出现了,你的 hotfix 分支修复后,指针位于 C4: .. image:: pics/branch-02.svg :width: 80% :align: center 既然 hotfix 分支完成了它的使命,那么就需要将它的内容 `merge` 到 master 分支,并在成功合并后删除它: .. warning:: 这只是最普通的合并分支做法;关于如何选择恰当的工作流(workflow)来规范管理与协调各个分支,请参考 :ref:`branch-workflow` 这一节的内容。例如,有许多工作流并不推荐使用 fast-forward 合并。 .. code-block:: sh $ git checkout master $ git merge hotfix $ git branch -d hotfix 该合并是一个典型的 fast-forward 合并,即发生合并操作的两个分支之间没有分叉(即 master 没有在 C2 之后的提交;此时合并操作只需要确认无冲突后,移动 master 指针到 hotfix 指针所在位置即可)。 好了,现在你完成了 issue 17 的热更新,继续回到你的 dev 分支工作。不多久,你完成了你在 dev 分支的工作,也就是提交 C5: .. code-block:: sh $ git checkout dev ... $ git commit -a -m "New features & bugs: ..." 注意到 master 位于 C4 而不再是 C2,这是因为与已被删除的 hotfix 分支合并过: .. image:: pics/branch-03.svg :width: 80% :align: center 现在需要开始一次新的合并了,切换到 master 分支以进行合并: .. code-block:: sh $ git checkout master $ git merge dev 这次分支合并主要涉及到三个提交点,在上图中已经标出: * C4:master 分支当前位置; * C5:dev 分支当前位置; * C2:两个分支的共同祖先(common ancestor)。 此时的合并是一个三方合并(three-way merge),无法通过简单地移动指针来完成。因此,git 会新建一个合并提交(merge commit)C6,其特点是拥有两个父提交。 .. image:: pics/branch-04.svg :width: 80% :align: center 由于我们在 master 分支上进行合并,因此 C4 称为第一父提交,C5 称为第二父提交。 删除分支:branch -d ----------------------- 如果没有冲突(单人工作时往往不会产生冲突),就能成功合并。上例合并后,你可以删除 dev 分支。 .. code-block:: sh $ git branch -d dev * 要删除未合并过的分支,使用 `-D` 选项代替 `-d` 选项。 * 要删除远程仓库中的分支,使用带 `--delete` 选项的 `push` 命令。比如,删除 origin 远程仓库中的 issuefix 分支: .. code-block:: sh $ git push origin --delete issuefix 查看分支列表:branch -v ----------------------------- 使用 `git branch` 命令查看分支列表,带“*”的是当前分支(即 HEAD 所在分支): .. code-block:: sh $ git branch * dev master 使用 `-v` 选项来查看每个分支的最后一次提交: .. code-block:: sh $ git branch -v * dev e6c4681 Attempt to fix center-aligning of picture in Markdown. master bae6fc8 Init 使用 `--merged` 选项以只显示完全合并到当前分支的分支。这个列表中的分支与你的当前分支没有分叉,且落后于当前分支;无特殊情况下,它们可以被删除(比如上文的 hotfix 分支已完全合并到 master 分支,可以删除)。`--no-merged` 选项则相反。 .. code-block:: sh $ git branch --merged $ git branch --no-merged 注意:如果你尝试用 `-d` 选项删除分支,但这个分支位于 `--no-merged` 列表中,git 会阻止你的删除操作(因为这意味着这个分支中的工作会丢失)。不过你总能使用 `-D` 选项来强制进行删除操作。 最后,使用 `-vv` 选项可以查看跟踪(上游)分支的信息。关于跟踪分支,参考 :ref:`tracking-branch` 一节。 .. _tracking-branch: 远程:跟踪分支(上游分支) ---------------------------- 跟踪分支也叫上游分支。当你从远程仓库克隆了分支,你的这个本地分支会自动设置为跟踪该远程仓库的对应分支。这是你就可以使用 `git pull` 命令方便地进行本地仓库更新(参考 :ref:`fetch-and-pull` 部分的内容)。 如果你的需要手动设置本地分支跟踪远程分支,一种方法是使用带 ``--track`` 参数的 ``checkout`` 命令来新建一个本地分支,并使其跟踪远程分支: .. code-block:: sh $ git checkout --track origin/issuefix 这个操作会在本地创建一个 issuefix 分支,并设置其跟踪 origin 远程仓库中的对应分支。实质上,该命令是下面这条命令的简写: .. code-block:: sh $ git checkout -b issuefix origin/issuefix 更改紧跟 `-b` 选项后的 issuefix,就能将本地分支设置成另外的名称。 另一种设置跟踪分支的方法是使用 ``branch`` 命令的 ``--set-upstream-to/-u`` 参数: .. code-block:: sh $ git checkout -b dev $ git branch -u origin/dev 如果不想切换到 dev 分支操作,你也可以在其他分支使用类似第二行的命令,只不过要在末尾额外添加一个分支名: .. code-block:: sh $ git branch -u origin/dev dev 设置了跟踪后,用 `@{upstream}` 或 `@{u}` 来指代远程分支。例如,在你设置 issuefix 分支跟踪对应远程分之后,你可以使用 `git merge @{u}` 代替 `git merge origin/issuefix`。 在上文已经介绍过,可以通过 `branch` 命令的 `-vv` 选项来查看跟踪分支的信息: .. code-block:: sh $ git branch -vv * dev e6c4681 Attempt to fix center-aligning of picture in Markdown. master bae6fc8 [origin/master] Init 以上表示我的 dev 分支尚未设置跟踪远程分支。 .. _basic-conflict: 冲突处理 -------------- 在合并分支时,如果存在分叉,那么可能会有冲突(conflict)。冲突是指在不同的分支中,同一个文件的同一部分(比如同一行)被以不同的方式修改了。此时如果使用 `git merge` 命令,git 会在检测到冲突后自动暂停合并,弹出合并工具界面,等待用户解决。 参考官方手册得来的一个冲突提示样例: .. code-block:: <<<<<<< HEAD:index.html
======= >>>>>>> dev:index.html 上述信息表示,`HEAD` 指针指向的版本(目前在 master 分支上,因为之前我们在尝试进行合并操作之前切换到了 master 分支)的内容如 “=======” 上方的内容所示;而 dev 分支的同一部分内容却如其下方所示. 要解决冲突,通常的做法是选择其中一个分支保留(当然,你也可以自行输入内容): 1. 确定要保留哪个分支的内容; 2. 将 "=======" 另一侧的所有行删除; 3. 将 "<<<<<<<", "=======" 与 ">>>>>>>" 所在的行删除。 例如,上文如果要保留 dev 分支的内容,那么就更改为: .. code-block:: html 然后你可以退出合并工具界面了,告诉 git 已经解决了冲突。 想了解更复杂的冲突处理,参考 :ref:`advanced-conflict` 部分的内容。 .. _branch-workflow: 分支工作流 --------------- .. important:: 选择一个合适的分支工作流是高效管理项目开发的重要步骤。 不同的项目可能适用不同的分支策略(branching scheme)。本文主要介绍 Git workflow 与 Github workflow 两种工作流。 Git 工作流 ^^^^^^^^^^^^^^^^^^ `Git Workflow