系列文章原载于自己的博客,TOPI.CO (http://topi.co) ,某天不小心就 push 错啦,懒得从头再来,上传到 Ruby-China 来,一是方便自己回顾,另外也方便跟我一样的初学者
很多时候我们是多个人同时为做一件事情而努力,如何有效化解多人协同运作过程中出现的种种矛盾是相当重要的。
###基于 Git 的团队协同开发
设想仓库放在 GitHub 上,地址为[email protected]:username/projectname.git
。可以用以下命令导出工作树到本地。
$ cd work
$ git clone [email protected]:username/porjectname.git project_dir
git-clone 可利用各种网络协议访问远端机器中的 Git 仓库,从中导出完整的工作树到本地。在上述示例中,通过 SSH 协议访问了位于 github 上的项目仓库。从而在当前目录下建立了 project_dir 工作树。
若上述命令中未指定本地工作树名,那么 git-clone 会在当前所在目录中建立与工作树同名的工作树,即 projectname。
git-clone 命令只要碰到类似以下格式的远端仓库地址,它就会认为该地址是符合 SSH 协议的。
账户@IP:工作树路径
既已获得 M2GE 工作树,就可以开始协同工作了。
$CD ~/path/to/project
$git push [email protected]:username/projectname.git master
如果是 Fork 的项目,参见如何 Create and Fork 项目
$ cd ~/path/to/project
$ git pull [email protected]:username/projectname.git
git-pull 命令可将属于同一项目的远端仓库与同样属于同一项目的本地仓库进行合并,它包含了两个操作: + 从远端仓库中取出更新版本 + 然后合并到本地仓库
###如何解决仓库合并冲突
但是多人对一些相同的文件进行了改动,那么在合并时必然会遭遇合并冲突的问题,此时手动修改发生合并冲突的文件,然后将结果提交到本地仓库。
现在假设 Jack 与 Rose 在各自的工作树中对同一份文件 foo.txt 进行了修改,而 foo.txt 原内容如下:
one
two
three
Jack 对 foo.txt 进行了如下改动,并将该改动提交到本地仓库。
ONE
two
three
Rose 对 foo.txt 进行了以下改动,也将该改动提交到本地仓库。
one
two
THREE
当合并 Jack 的 Rose 仓库时,会自动合并二人对 foo.txt 的修改:
ONE
two
THREE
现在工作树中的 foo.txt 文件即包含了 Jack 的改动,也包含了 Rose 的改动,而且合并结果自动作为新版本提交到 Lyr 的仓库中。
观察上述合并冲突示例,可以看出,虽然 Jack 与 Rose 是对同一份文件进行了修改,但是他们的修改并未重叠。
现在假设二人对 foo.txt 的同一行做出了修改,那么仓库合并时会发生什么,应当如何处理呢?假设 Rose 对 foo.txt 修改如下 (还记得吗,Jack 将one
修改成ONE
):
one ONE
two
three
当合并时,会给出以下反馈信息:
Auto-merged foo.txt
CONFLICT (content): Merge conflict in foo
Automatic merge failed; fix conflicts and then commit the result.
上述信息之意是:尝试合并 foo.txt 文件的改动发生了冲突,自动合并失败,请用户手动修复冲突然后将结果提交到仓库中。
看到上述信息,可打开合并后的 foo.txt,可看到了以下内容:
<<<<<<< HEAD:foo
ONE
=======
one ONE
>>>>>>> 1116d3270764d91a25532a753a47b8b0e1b6f1b8:foo
two
three
以一串 < 开头的字串表示 Jack 的当前项目版本对 foo.txt 的修改结果。
而以一串 > 开头的字串表示 Rose 的当前项目版本对 foo.txt 的修改结果。
中间用了一串= 号将二人修改结果隔开。
一旦理解了版本冲突的表示格式,就很容易地根据现实情况将合并冲突问题解决掉。如果认为 Jack 的改动是不符合项目需求的, 可按照项目的实际需求进行了手工合并。然后,将合并处理结果提交到仓库中,即完成了重叠冲突的合并问题的解决。
多人的项目开发流程大致如下:
$ git clone [email protected]:username/projectname.git
... 项目开发 ...
$ git add 改动的文件
$ git commit
$ git pull
... 解决版本合并问题 ...
$ git push
基于 Git 最基本的多人协同工作模式,需记牢三个 Git 命令:git-clone -> git-pull -> git-push
。
###项目分支管理
Git 最为世人称道的就是它那强大的项目分支管理功能,现在较为流行的版本控制系统,诸如 CVS、SVN 等,虽然也提供了项目分支管理功能,但是可用性极低。
对于 Git 而言,管理多个项目分支如探囊取物耳,游刃有余。
前面一直未提及项目分支问题,但事实上是有一个分支存在的,那就是master
分支 (主分支) 该分支是由 Git 自动产生的。
在此之前,我们针对项目版本的各种操作都是在主分支上进行的,只是我们未察觉它的存在而已。
Git 可以轻松地产生新的项目分支,譬如下面操作可添加一个名曰“local”的新的项目分支:
$ git branch local
对于新产生的 local 分支,初始时是完全等同于主分支的。但是,local 分支在所进行的所有版本更新工作都不影响主分支, 这意味着作为项目的参与者,可以在 local 中开始各种各样的更新尝试。
查看项目仓库中存在多少分支,可直接使用 git-branch 命令,譬如使用该命令查看 projectname 项目分支列表:
$ git branch
local
* master
在上述操作输出结果中,若分支名之前存在 * 符号,表示此分支为当前分支。其实 Git 各分支不存在尊卑之别,只存在哪个分支是当前分支的区别。为了某种良好的秩序,很多人默认是将 master 分支视为主分支,
使用git branch local
操作输出的分支列表可以看出,虽然产生了 local 分支,但是 Git 不会自动将当前分支切换到 local 下。
可使用`git-checkout`命令实现分支切换,下面操作将当前分支切换为前文所产生的 local 分支:
$ git checkout local
我们产生了 local 分支,并在该分支下进行了诸多修改与数次的版本更新提交,但是该如何将这一分支的最终状态提交到 master 分支中呢?git-merge
命令可实现两个分支的合并。
譬如我们将 local 分支与 master 分支合并,操作如下:
$ git checkout master # 将当前分支切换为master
$ git merge local # 将local分支与当前分支合并
当一个分支检查无误并且与 master 分支成功合并完毕后,那么这一分支基本上就没有存在的必要性了,可以删除掉:
$ git branch -d local
注意,git-branch 的 -d 选项只能删除已经参与了合并的分支,对于未有合并的分支是无法删除的。如果想不问青红皂白地删除一个分支,可以使用 git-branch 的-D 选项。
现在来讨论一下如何基于 Git 项目分支管理功能实现更为稳健、高效的协同开发机制。
GitHub 服务器上已经建立了 projectname 仓库。
现在以 Jack 作为主角,看一看他围绕 projectname 开发工作的一天中的工作过程。
首先,需要更新自己机器上的工作树,Jack 查看实验室其他成员的版本更新信息:
$ git pull
$ git log
然后,开始建立一个新的项目分支,并将其命名为 jbranch,并将当前分支切换为该分支:
$ git branch jbranch
$ git checkout jbranch
然后这一天中剩余的大部分时间,都在自己所建立的项目分支上工作,jack 如增加了 3 个新的接口及相关测试程序,并对原有接口做了一些修改。一天的工作完成后,他有必要将这一天的工作与 projectname 仓库的 master 分支进行合并,然后删除 jbranch 分支:
$ git checkout
$ git merge jbranch
$ git branch -d jbranch
现在,已经将这一天的工作反映到自己机器上的 projectname master 分支上了,Jack 最后要做的是将其推送到服务器的 projectname 仓库,以使项目其他成员能够分享他的工作。
这里要注意,在推送版本更新之前,需要使用 git-pull 命令将这一天中其他成员对服务器端的 projectname 的更新拉过来合并到自己的 master 分支,然后才可以将自己的版本更新推送到服务器上的 projectname 仓库,具体操作如下:
+ 使用 git-pull 命令更新本地工作树; + 若出现版本合并冲突,并且 Git 无法自动合并,需要手工合并,然后将合并结果提交到本地 master 分支; + 使用 git-push 命令将本地 master 分支更新推送到服务器 projectname 仓库中。
目前,对于我们而言,在基于 Git 的 M2GE 协同开发过程中,引入分支管理功能,可有效防止因个人操作不当而导致向服务器 projectname 仓库提交太多的脏数据。另外,也有效保持了本地项目主分支的干净,避免了频繁 git-clone 服务器端的 projectname 仓库来恢复本地的项目主分支。