持续部署的前提是模块化设计、自动化测试和持续集成。使用好 Git 的分支(branch)与整合(integrate)功能,有利于高效率的持续集成。Pro Git 关于 分支 的建议是不错的,值得采用。
整合
Rebasing(变基) 是一个很不错的功能,和 Merging(合并) 是 一对兄弟,是用来整合来自不同分支的修改的两种方法。
Rebasing
对于多人协同开发来说,本地修改 commit(提交) 后,在 push(推送) 代码前,Rebasing 一下是一个很好的做法,log 看起来比较整齐,有助于阅读和理解(由于 merge 带来了蜘蛛网般的 log,想看清代码走向非常费劲;log 中常见的 "Merge branch 'v0.7d-rebuild' of http://git.example.com/wbs.wph into v0.7d-rebuild" ,本是不必要的 merge,也自然消失了)。
请不要对已经 push 出去的东西 再次 rebase 了。
怎么理解 Rebase 呢?
假定把当前分支作为新特性分支,在开发过程中,主分支(Rebase 分支)又发生变化了,我们就要基于 Rebase 分支最新末端重新应用一下当前分支的每个 commit。
同理,rebase 概念适用于有跟踪关系的远程和本地分支的整合。
在 config 中设置 rebase 选项,使得 git pull 使用 rebase 整合所做修改
git config --global pull.rebase true
git config --global branch.autoSetupRebase always
- pull.rebase:git pull 时使用 rebase 而非 merge 来整合修改;
- branch.autoSetupRebase:对所有的 tracking branches 都设置 rebase;
- 在 git config 时加上 --global 选项,对所有 repo 起作用;
- 单次操作可以这么着:git pull --rebase [<remote name> <branch name>];
- pull with rebase @ gitready.com ;
采用 merge 示意图
git pull 命令采用 merge 策略,即:pull(拉取)= fetch(抓取)+ merge;
采用 rebase 示意图
git pull 命令采用 rebase 策略,即:pull(拉取)= fetch(抓取)+ rebase;
变基时出现冲突,怎么办?
- 镇定,冲突总能解决;
- 耐心,避免引入更多缺陷;
- 首先修改文件,解决冲突;
- 然后 add,commit 修改后的文件;
- 最后运行
git rebase --continue
(请不要轻易使用 skip or abort);When you have resolved this problem, run "git rebase --continue".
If you prefer to skip this patch, run "git rebase --skip" instead.
To check out the original branch and stop rebasing, run "git rebase --abort".
冲突产生的原因
- 多人开发,有冲突是不可避免的;
- 学会解决冲突也是一项重要技能;
- 发生冲突通常是因为两个人修改了同一个文件;
- 如果经常出现冲突,通常说明你的用法有问题,请赶紧学习;
冲突的解决之道
- 尽早集成(smaller changesets),有助于避免冲突,即使出现冲突也容易解决;
- 每次 pull(fetch+rebase),就是一次(代码)集成;
请经常 pull;不一定非要到 push 前才去 pull;
经常 pull,经常集成(integrating often),使得 push 不再那么匆忙(有因怕冲突而匆忙 push 的情形);当且仅当需要 push 的时候才去 push; - 每次 push,都是一次发布;
- 对于解决冲突的 commits,一定要 review code!
git log --min-parents=2 -p --cc
删除分支的理由和时机
- 当你的两个分支指向同一个 commit 时,就可以删除其中一个;无冗余,保持整洁了;
- 当你的一个分支 A 已经完全合并到另一个分支 B 上,A 分支使命已经完成、不再使用时,就可以删除这个分支 A;
请使用 git branch --merged 和 git branch --no-merged 来检查是否已合并到当前分支; - 你觉得碍眼,就想任性一下要删除时;
删除没有合并的分支时,git 会提醒你,当然也可以强行删除;
了解 remote 的分支情况
- git ls-remote [<remote>]
- git remote show [<remote>]
A commit and its tree |
---|
选自 Branches in a Nutshell,理解 git 存储机制 |
分支管理策略(Branching Workflows)
- master 分支
- dev(develop) 分支是我们的主要分支,在当下,上线发布使用 dev 分支;
- 从 dev 开分支 hotfix;
- 迭代分支(feature、topic)从 dev 分支开出,我们使用 0.8, 0.8c 这样的方式命名;
- 迭代分支应经常 merge dev(git merge dev);(迭代完成后)dev 合并迭代;删除迭代分支;
- 保持适度分支,避免复杂过程;
作者 nvie 在 这篇文章 里阐述的更多;
merge 示意图
Git 会使用两个分支的末端所指的快照(C4 和 C5)以及这两个分支的共同祖先 Common Ancestor(C2),做一个简单的三方合并。
高级主题
-
Deleting Remote Branches
git push origin --delete <branchname>
- distributed git
- 重命名分支名字(修改分支名字)
git branch -m [<oldname>] <newname> 修改本地 oldname => newname
;
git push -u origin <newname>
推送到远程;
git push origin :<oldname>
删除远程的 oldname; -
git branch
-vv
-v, --verbose
When in list mode, show sha1 and commit subject line for each head, along with relationship to upstream branch (if any). If given twice, print the name of the upstream branch, as well (see also git remote show <remote>). -
git log --decorate --oneline --all --graph
查看所有分支(图形形式); -
git branch -d <branchname>
如果你的 repo 是 clone 来的,并且和上游保持同步联系,那么删除本地分支名,就是为了清爽,你随时可以 checkout 出来。有时会报警告:warning: deleting branch 'v0.7d-example' that has been merged to 'refs/remotes/origin/v0.7d-example', but not yet merged to HEAD.
-
git branch -d -r <branchname>
delete remote-tracking branches. 这只是删除本地记录的那个远程分支名;要真正删除远程分支,请使用git push :<branchname>
; -
git fetch --prune
-p,--prune,After fetching, remove any remote-tracking references that no longer exist on the remote. git fetch;
如果有人清理了远程 repo 分支,你 git pull 后 并不会同步清理 本地记录的那些远程分支名;请使用该命令清理那些在远程并不存在的refs;
git remote prune origin --dry-run
,检查清理 origin 无效分支,git remote update --prune origin
也可以; - Learn Version Control with Git;
- 讨论 Martin Fowler 对持续集成和特性分支看法的文章,内容丰富,值得看;
- Branching and Merging Primer,August 2006, Chris Birmele @ Microsoft
-
可能无法删除远程分支?
通常是因为该分支是当前活跃分支。在远程 bare repo 下,执行:git symbolic-ref HEAD refs/heads/<anotherbranchname>
,也就是改换 HEAD 指向另一个分支,然后就可以删除你想删除的分支了;或者直接修改 HEAD 文件; - 只有部分分支推送成功的问题
有时候,你修改本地一个分支(v0.8分支),直接 push,发现这个分支成功推送,而另一个分支(dev分支)出了问题,如下:
$ git push
Counting objects: 2, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (2/2), done.
Writing objects: 100% (2/2), 257 bytes | 0 bytes/s, done.
Total 2 (delta 1), reused 0 (delta 0)
To http://git.example.com/wbs.wph.git
0187b7f..9c68fb9 v0.8 -> v0.8
! [rejected] dev -> dev (non-fast-forward)
error: failed to push some refs to 'http://git.xxtao.com/wbs.wph.git'
hint: Updates were rejected because a pushed branch tip is behind its remote
hint: counterpart. Check out this branch and integrate the remote changes
hint: (e.g. 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.
- 请每次都要仔细查看返回的信息,git 做的非常好的一点就是会认认真真地告诉你下一步应该怎么做,你照着做就好了。
- 照着做尽管并不是 100% 都成功,无论如何值得认真尝试;