使用Git作为版本控制器,有众多可能的工作流(Workflow),这使得我们这些新鸟不知道在实际工作中不知道该选择哪种工作流。这里我们对最常见的Git工作流做一个对比,为企业团队提供一个参考。
中心化的工作流(Centralized Workflow)
转换到分布式的版本控制系统,看起来可能是一个令人生怯的任务,但是你并不是必须要通过改变你先现有的工作流来发挥Git的优势。你的团队可以使用和Subversion一样的方式来开发项目。
但是,使用Git来运转你们的开发工作流还是会表现出一些SVN没有的优点。首先,Git让每个开发者在本地有一个完整的项目复本。这个独立于中心库的环境使得每个开发者和其他开发者对项目的修改独立开来——他们可以在本地库上进行提交操作,而且他们可以完全不上传他们的开发,直到他们认为是上传的时机到了再上传。其次,Git为你提供了使用Git强大的分支和合并模型。不像SVN,为了方便Git代码的集成,以及在库与库之间分享修改,Git的分支被设计成一种fail-safe的机制。
How It Works
和Subversion一样,中心化的工作流使用一个中心库作为所有代码对项目修改的一个单点入口。这里抛弃trunk的概念,默认的开发分支被称为 master分支,所有对项目的修改都被提交到这个分支。这个工作流不需要其他除master分支以外的任何分支。
开发者的开发从克隆中心库开始。在他们的本地项目复本中,他们可以和使用SVN一样,进行修改文件,提交修改的操作;但是,不同的是,这些新的提交被保存在本地——也就说这个本地副本和中心库是完全独立的。这种方式允许开发者延迟将本地的修改上传,直到一个合适的时间点再上传所有的commmits到中心库。
为了将所有本地的修改发布到官方项目(中心库)上,开发者需要将他们的本地master分支”push“到中心库。这和svn的commmit是等效的,不同之处在于git提交的是所有在本地存在而中心库没有的commit。
冲突管理
中心库代表的是官方项目,所以中心库的提交历史需要被视为庄严的且不可更改的。如过一个开发者的本地commits偏离了(diverge)中心库,git将拒绝他的push操作,因为这个操作会导致对官方提交的覆盖。
在开发者可以发布他的特性之前,他们需要获取中心库中提交,且把本地的修改重新定基(rebase)在中心库之上。这就好比再说:”我想把我的修改添加到其他人已经做的修改之上。“正如传统的SVN工作流一样,这样操作的结果是一个完美的线性历史。
如过本地的修改和上流的提交产生直接的冲突,Git将暂停重新定基(rebase)操作,并且给你机会手动解决冲突。比较好的一点是,在解决冲突的时候,使用git status 和 git add命令的方式是不变的。这有利于开发者方便的管理自己的冲突。另外,如过他们方法自己陷入到解决冲突的麻烦中,Git提供了方便的方式来放弃当前rebase,也很方便的可以再次rebase(当然,也可以去寻求他人的帮助)。
示例
我们来一步一步的操作,看看典型的小团队是怎样使用这个工作流的。我们会看到两个开发者(Hohn和Mary)是如何在同一个特性上工作,并在同一个中心库上分享自己的贡献。
初始化中心库
首先,某人需要在魔偶个服务器上创建一个中心库。如过这是个新项目,你可以初始化一个空的库。否则,你需要从显存的Git或SVN库中导入。
中心化的仓库总是从一个空库(没有工作目录)开始的,使用下面的方式创建:
ssh user@host git init --bare /path/to/repo.git
请确保你提供的是一个合法的SSH用户名,host用服务器域名或者IP地址代替,最后选择一个你想来存放库的路径。
每个人从中心库克隆
从中心库克隆之后额,每个开发者都创建了一个完整项目的本地复本。这是通过git clone 命令完成的:
1 git clone ssh://user@host/path/to/repo.git
当你完成对中心库的clone之后,Git会自动的添加一个名叫origin的捷径,这个origin会指向”父亲“仓库。创建这个捷径是基于你将会和”父亲“仓库进行交互的假设。
John开发他的特性
在John的本地仓库,John想是用标准的Git commit流程(edit,stage,和commit)来开发他的特性。如过你对stage不熟悉,请记住他是一种并不需要包含对工作目录的所有修改的提交方式。这使得即使你进行了大量的本地修改,你依然可以进行高度集中的提交。
1 git status # VIew the state of the repo2 git add# Stage a file3 git commit # Commit a file
记住一点,由于这些命令创建的是本地提交操作,John可以重复这些过程很多次,而不用担心在中心库上正在发生些生么。这个对一个大特性的开发是十分有用的,因为大特写需要分解成很多更简单、更原子化的块。
Mary在开发她的特性
John发布他的特性
1 git push origin master
这里的origin和中心库的远程连接,这个在John克隆中心库的时候创建的。参数master告诉Git,试着让中心库的master分支和John的本地master分支一样。因为中心库从John克隆到现在还没有被更新过,所以这不会导致任何的冲突,这次push操作会和预期的一样,被成功执行。
Mary尝试发布她的特性
让我们看看,在John成功发布他的修改到中心库后,如过Mary尝试push她的特性会发生什么。她使用同样的命令来push:
1 git push origin master
但是,由于他的本地历史偏离了中心库,GIt会拒绝她的请求,并返回如下复杂的错误信息:
error: failed to push some refs to '/path/to/repo.git'hint: Updates were rejected because the tip of your current branch is behindhint: its remote counterpart. Merge the remote changes (e.g. 'git pull')hint: before pushing again.hint: See the 'Note about fast-forwards' in 'git push --help' for details.
Mary在John的commit上面rebase
1 git pull --rebase origin master
这里--rebase的选项告诉Git将Mary所有的commit移动到和中心库的修改同步后的master分支的末端,如下图所示:
没有--rebase选项,pull同样会起作用,但是这样做的结果是,每次有人要和中心库同步的时候,都是产生一个不必要的merge commit。对于这个工作流,用rebase代替merge commit是最佳的选择。
Mary解决一次merge冲突
Rebase的过程将所有本地的提交一个接一个(每次一个)的转移到更新后的master分支的过程。这意味这你得到的冲突时基于commit-by-commit的,而不是在整个修改上的。这样就保证了工作尽可能的聚焦在你的提交上,从而保证了一个干净的项目历史。相应的,这样做使得找出bug在哪里传参数跟更加容易。如过必要,可以对项目产生最小影响的情况下进行回滚操作。
如过Mary和John是在开发不相关de特性,那么在rebase的过程中产生冲突的可能性不大。但是如果产生了,GIt会在产生冲突的当前commit处暂停rebase,并输出如下的消息,同时给出相关的解决建议:
1 CONFLICT (content): Merge conflict in
Git的伟大之处在于任何人都可以解决他们自己合并中遇到的冲突。在我们的例子中,Mary会通过git status命令查看问题发生在哪里。产生冲突的文件会显示在Unmerged路径部分:
1 # Unmerged paths:2 # (use "git reset HEAD..." to unstage)3 # (use "git add/rm ..." as appropriate to mark resolution)4 #5 # both modified:
1 git add2 git rebase --continue
这便是解决冲突的所有过程了。Git将会继续对下一个commit进行重复的过程,对其他产生冲突的commit使用同样的流程处理。
如过在这个过程中,你觉得没办法处理了,不要害怕。只要指向下面的命令,你就可以回到你在运行 git pullo --rebae之前了:
git rebase --abort
这便是解决冲突的所有过程了。Git将会继续对下一个commit进行重复的过程,对其他产生冲突的commit使用同样的流程处理。
如过在这个过程中,你觉得没办法处理了,不要害怕。只要指向下面的命令,你就可以回到你在运行 git pull --rebae之前了:
git rebase --abort
Marry成功发布她的特性
她完成和中心库的同步后,Mary便可以成功发布他对项目的修改了:
git push origin master
从这里,我们可以到哪儿?
正如你看到的那样,可以通过价格简单的Git命令,便可以复用传统的Subversion开发环境。对于从SVN转移过来的团队,这是很了不起的,但是这却没有充分利用到Git分布式的特性。
如过你的团队乐于使用中心化的工作流,但那时又想简单化团队协作,那么你们可以去探索下特性分支工作流(Feature Branch Workflow)。通过专注在一个独立的分支上开发一个特性,可以在将这些新东西集成到官方项目之前对他们进行升入的讨论。