Git教程 · 相同分支上的开发
在 Subversion 这样的集中式版本控制系统中,团队通常是在一个公用分支上进行操作的。 所有开发者都需要将这个分支复制到本地工作区,然后在本地进行修改,之后再将它们写回该版本控制系统。
我们也可以用 Git 来完成一个非常类似的工作流。通过这种相似性,我们可以让自己更容易、更快速地过渡到分布式版本控制上来。每个开发者都可以创建一份中央版本库的克隆体,并在这些副本的 master
分支上执行自己的操作。
一旦他们的开发成果已经准备好要提供给他人使用,本地 master
分支就需要与中央版本库的 master
分支进行合并。这就会产生一次合并提交,而与此同时其他开发者也会做出相关的修改。该合并操作会在本地版本库中完成,然后再将结果传送到中央版本库中去 。
本章工作流的优点就是当某个开发者所做的修改经常与其他开发者的修改混淆在一起的时候,有利于冲突的快速检测。当然另一方面,该工作流程也会创建出许多合并提交,从而使得提交历史显得较为混乱。我们在“基于特性分支的开发”这类父级历史优先的工作流中将不会再用到它。
本章所要介绍的工作流将包含以下内容。
- 如何在本地的
master
分支上进行开发工作。 - 如何将自己的成果发布到中央版本库中。
- 如何让其他开发者使用这些成果。
1️⃣ 概述
在下图中,我们会看到本章工作流的典型使用情况。如你所见,中央版本库位于该图的顶部,底部则是开发者A 和开发者B 的版本库。
每个开发者都会从自己的 master
分支开始着手,所产生出小型的增量提交必须要能够返回到之前的状态,并将自己的步骤文档化。
一旦任务完成,或者有其他开发者所需要的中间层代码,该提交就会通过push
命令被传送到中央版本库中。只要在此期间,中央版本库的 master
分支没有发生变化,该 push
命令就会成功被执行。
但通常情况下,中央版本库的 master
分支上应该还会有来自其他开发者的提交。在这种情况下,我们必须要先用 pull
命令将中央版本库中的修改合并到本地版本库中。这样,我们就在本地版本库中创建了一次合并提交,然后再通过push
命令将该提交传送给中央版本库, 正如你在图中看到的那次最后的合并提交。开发者A 之前已经上传了自己的提交,而开发者B 则必须要先将自己的提交与现有的提交合并起来。
当然,开发者也可以只用 pull
命令将其他开发者所做的修改纳入到自己的版本库中来。
2️⃣ 使用要求
-
提交历史并不是非得“赏心悦目”:提交历史只需要充当安全网的角色,防止数据丢失,并能与旧版本进行比较即可。当然,在工作流 “基于特性分支的开发” 中,我们会看到提交历史的另一种可能的应用。
-
持续集成中央版本库的
master
分支:本章的工作流会创建大量的合并提交,所以它总是存在着某种风险,以至于会产生一些有问题的合并版本。正因为如此,我们更要 对中央版本库的master
分支上的内容进行反复的构建,并对其进行持续的测试,以使开发者们能及时了解其中随时可能出现的问题。
3️⃣ 执行过程及其实现
在本章的工作流中,所有开发者都将先在本地版本库的同一分支上开展工作,然后将工作结果合并到中央版本库的 master
分支上去。
对于接下来的执行过程,我们的开发要从将中央版本库克隆到本地开始。
在 master
分支上操作:
-
第1步:更新 master 分支
开始一个新任务之前,我们往往应该先去获取中央版本库中的最新版本,这样做有助于我们将文件冲突的风险降到最低。
在这个过程中,如果我们想要确保某些本地提交不会被传送给中央版本库,可以在命令后面加上--ff-only
参数。该参数可以防止Git 自动合并我们从中央版本库中获取的修改。> git pull --ff-only
在这里,
--ff-only
参数是通过在pull
命令中允许快进式合并来防止出现合并提交的。 -
第2步:进行本地更改
在拉取中央版本库master
分支①上的当前版本之后,相关的开发工作就可以在本地机器上展开了。
在这个过程中,我们往往会在 Git 中创建出一系列增量提交,其中会包含某个子任务、 某个重构单元或某个错误修复。按照这种方法做下去,开发者们就能随时返回到之前的某个版本,拿其中的文件与当前版本进行比较。另外,在进入到下一步骤之前,我们还必须要针对本地修改做一次提交,以终止当前步骤。> git commit -m "Method X revised"
-
第3步:将中央版本库中发生的修改合并到本地修改中
如果某些开发任务已经完成,或者有别的开发者需要我们提供某个中间版本,那么我们就需要将自己的本地修改上传给中央版本库。
由于我们在这里要求中央版本库中在同一时间内不发生更改,所以在进行下一步操作之前,最好再调用一下pull
命令,以获取中央版本库中所发生的所有变化。> git pull Already up-to-date.
如果在执行
pull
命令时,中央版本库中并没有发生变化,Git就会打印出 “up-to-date
” 的消息。而如果中央版本库中发生了变化,但 Git能够自动完成合并, Git 就会显示没有“冲突” 的消息,并打印出被更改文件的名称。remote: Counting objects: 5,done. remote: Compressing objects: 100% (2/2),done. remote: Total 3(delta 0),reused 0(delta 0) Unpacking objects: 100%(3/3),done. From projectx 2cd173f..e10bb4d master ->origin/master Merge made by recursive. foo | 1 + 1 files changed,1 insertions(+),0 deletions(-)
但两边的修改之间如果发生了冲突, Git就将这些内容显示在输出中。
remote: Counting objects: 8,done. remote: Compressing objects: 100%(3/3),done. remote: Total 5(delta 0),reused 0(delta 0) Unpacking objects: 100%(5/5),done. From projectX 9139636..fa60160 master ->origin/master Auto-merging foo
foo中的合并冲突是可以通过一般方法来解决的。先将之前未完成合并的文件调整之后再 用
add
命令重新添加到版本库中。然后,我们需要再用commit
命令来完成合并。> git add foo > git commit
在这里,如果我们在执行
commit
命令时没有提供相应的注释说明, Git 会根据当前的冲突内容自动生成一段相关的合并说明。Merge branch 'master'of projectx Conflicts: foo
-
第4步:将本地修改上传到中央版本库
在将该版本上传给中央版本库之前,我们应该在本地运行一些测试,检查一下是否会存在某些问题。
如果该版本的质量令人满意,我们就可以调用push
命令,将本地修改上传到中央版本库了。 如果上述步骤能成功完成,我们就会在本地版本库中看到一个合并完成了的项目状态。> git push
只要
push
命令没有报错并终止执行,就意味着我们已经成功将提交上传到了中央版本库中。
如果在此期间有另一个开发者上传了一次提交,push
命令就会终止执行,并返回以下错误消息:To projectX.git ![rejected] master -> master(non-fast-forward) error: failed to push some refs to '/Users/rene/temp/project.git/' To prevent you from losing history,non-fast-forward updates were rejected. Merge the remote changes(e.g.'git pull')before pushing again.See the 'Note about fast-forwards' section of 'git push --help' for details.
在这里,为了获取中央版本库中新的更改,我们就需要再次调用
pull
命令。这样就等于 又另外创建了一次合并提交。为了不让这两次合并提交把提交历史弄得更为复杂,我们可以 用reset
命令将第一个合并提交删除掉。> git reset --hard ORIG_HEAD
这上面的命令中,
--hard
参数会在指定的提交中对工作区和暂存区进行重构。而后面的 ORIG_HEAD 则是执行我们最后所执行的pull
或merge
命令所产生提交的符号名称。
完成上述动作之后,我们就相当于回到了第3步,用pull
命令再次从中央版本库中获取相关的修改。
4️⃣ 替代方案
本章所介绍的工作流会产生多次合并提交,从而使得提交历史变得非常难以阅读。对此,另一种做法改用变基的方式将中央版本库中的修改合并到本地修改中(即在执行 pull
命令时使用--rebase
参数)。
> git pull --rebase
通过对本地修改的变基,我们就可以再次提交中央版本库 master
分支上的提交了。也就是说,变基创建的是一次新的提交,但它可以包含相同的修改内容。
在下图中,我们可以看到两段不同的提交历史,分别由合并操作和变基操作产生。
一眼看去,变基操作的那段历史记录因其没有多余分叉的线性结构而格外引人注目。但 这个优点会被该历史中提交的即时性所掩盖,在这种情况下,开发者就无法在其本地环境中 获取各个版本的详细信息了。因为在执行变基操作的过程中,总会有多个提交对象被复制。
因此,开发者们只能查看最后提交中的内容。另外,这些被复制的还都是未经测试的版本。
以图中的提交 F’为例。该提交是由提交 G 变基而来的。只要后者在被复制到提交 F’的过程中没有引发冲突,该提交就会被忽略。然而,我们也可以想象,版本上所造成的误差也有可能会导致它根本不能工作。
如果我们只需要知道谁做了这些提交,这一切都没有问题。但一旦我们想依靠这段话提交历史来解决什么问题,这些提交就会开始让人头痛了。
如果我们在工作中会大量使用变基操作,也可以通过以下设置将其配置成 pull
命令的默 认行为:
> git config branch.master.rebase true
在这里, branch.master.rebase
参数决定了哪一个分支被启用为变基操作的默认分支。即 其分支名 ( master) 可以被替换成任何其他的分支名。