Git 的使用
Git 使用规范流程
下面是 ThoughtBot 的 Git 的使用规范流程:
第一步:新建分支
首先,每次开发新功能,都应该新建一个单独的分支
1 | 获取主干最新代码 |
分支管理策略
- 主分支 Master
- 开发分支 Develop
这个分支可以用来生成代码的最新隔夜版本(nightly)。如果想正式对外发布,就在 Master 分支上,对 Develop 分支进行“合并”(merge)。
1
2
3
4
5
6
7
8
9 # 创建 develop 分支
git checkout -b develop master
# 切换至 master 分支
git checkout master
# 对 develop 分支进行合并
git merge --no-ff develop
## --no-ff 默认情况为“快进式合并”(fast-farward merge),会直接将 master 分支指向 develop 分支;使用 --no-ff 参数会执行正常合并,在 master 分支上生成一个新节点,保证了版本演进的清晰
- 临时性分支
应对一些特定目的的版本开发,临时性分支主要有:功能分支(feature)、预发布分支(release)、修补 bug(fixbug)。为临时需要,使用完以后应该删除。
- 功能分支
1
2
3
4
5
6
7 git check out -b feature-x develop
git check out develop
git merge --no-ff feature-x
git branch -d feature-x
- 预发布分支
1
2
3
4
5
6
7
8
9 git checkout -b release-1.2 develop
git checkout master
git merge --no-ff release-1.2
git tag -a 1.2
git checkout develop
git merge --no-ff release-1.2
git branch -d release-1.2
- 修补 bug 分支
1
2
3
4
5
6
7
8
9
10
11 git checkout -b fixbug-0.1 master
git checkout master
git merge --no-ff fixbug-0.1
git tag -a 0.1.1
git checkout develop
git merge --no-ff fixbug-0.1
git branch -d fixbug-0.1
第二步:提交分支 commit
分支修改后,就可以提交 commit 了。
1 | git add --all |
第三步:撰写提交信息
提交 commit 时,必须给出完整扼要的提交信息,下面是一个范本:
1 | Present-tense summary under 50 characters |
第一行是不超过50个字的提要,然后空一行,罗列出改动原因、主要变动、以及需要注意的问题。最后,提供对应的网址(比如 bug ticket)。
第四步:与主干同步
1 | git fetch origin |
第五步:合并 commit
分支开发完,很可能有一堆 commit,但是合并到主干的时候,往往希望只有一个(或最多两三个)commit,这样不仅清晰,也容易管理。
1 | git rebase -i origin/master |
git rebase
命令的参数i
表示互动(interactive),这时 git 会打开一个互动界面,进行下一步操作。下面为 [Tute Costa] 的例子:
1 | pick 07c5abd Introduce OpenPGP and teach basic usage |
上面的互动界面中,先列出当前分支的最新的4个 commit(越下面越新)。每个 commit 前面有一个操作命令,默认是 pick,表示该行 commit 被选中,要进行 rebase 操作。
下面是可以使用的命令:
- pick:正常选中
- reword:选中,并且修改提交信息
- edit:选中,rebase 时会暂停,允许你修改这个 commit
- squash:选中,会将当前 commit 与上一个 commit 合并
- fixup:与 squash 相同,但不会保存当前 commit 的提交信息
- exec:执行其他 shell 命令
上面这 6 个命令当中,squash 和 fixup 可以用来合并 commit。先把需要合并的 commit 前面的动词,改成 squash (或者 s)。
1 | pick 07c5abd Introduce OpenPGP and teach basic usage |
这样一改,执行后,当前分支只会剩下两个 commit。第二行和第三行的 commit,都会合并到第一行的 commit。提交信息会同时包含,这三个 commit 的提交信息。
1 | This is a combination of 3 commits. |
如果将第三行的 squash 命令改成 fixup 命令。
1 | pick 07c5abd Introduce OpenPGP and teach basic usage |
运行结果相同,但新的提交信息里面,第三行 commit 的提交信息会被注释掉。
1 | This is a combination of 3 commits. |
Pony Foo 提出了另一种合并 commit 的简便方法,就是先撤销过去5个 commit,然后再建一个新的。
1 | git reset HEAD~5 |
squash 和 fixup 命令,还可以当做命令行参数使用,自动合并 commit
1 | git commit --fixup |
第六步:推送到远程仓库
1 | git push --force origin myfeature |
git push
命令要加上force参数,因为rebase
以后,分支历史改变了,跟远程分支不一定兼容,有可能要强行推送。
第七步:发出 Pull Request
提交到远程仓库后,就可以发出 Pull Request 到 master 分支,然后请求别人进行代码 review,确认可以合并到 master。
常用 Git 命令清单
Git 远程操作详解
git clone
1 | git clone <版本库地址> <本地目录名> |
git remote
为了便于管理,Git 要求每个远程主机都必须指定一个主机名,默认为 master。git remote
命令就用于管理主机名。
不带选项的时候,git remote
命令列出所有远程主机
1 | git remote |
1 | 使用`-v`选项,可以参看远程主机的网址。 |
1 | 指定远程主机名为 jQuery |
1 | 查看该主机的详细信息 |
1 | 添加远程主机 |
1 | 删除远程主机 |
1 | 远程主机的改名 |
git fetch
一旦远程主机的版本有了更新(Git 术语叫做 commit),需要将这些更新取回本地,这时就要用到git fetch
命令。
1 | git fetch <远程主机名> |
git fetch
命令通常用来查看其他人的进程,因为它取回的代码对你本地的开发代码没有影响。
默认情况下,get fetch
取回所有分支(branch)的更新。如果只想取回特定分支的更新,可以指定分支名。
1 | git fetch <远程主机名> <分支名> |
所取回的更新,在本地主机上要用“远程主机名/分支名”的形式读取。比如origin
主机的master
,就要用origin/master
读取。
git branch
命令的-r
选项,可以用来查看远程分支,-a
选项查看所有分支。
1 | git branch -r |
上面命令表示,本机主机的当前分支是master
,远程分支是origin/master
。
取回远程主机的更新以后,可以在它的基础上,使用git checkout
命令创建一个新的分支。
1 | git checkout -b newBranch origin/master |
上面命令表示,在origin/master
的基础上,创建一个新分支。
此外,也可以使用git merge
命令或者git rebase
命令,在本地分支上合并远程分支。
1 | git merge origin/master |
git pull
git pull
作用是取回远程主机某个分支的更新,再与本地的指定分支合并。它的完整格式稍稍有点复杂。
1 | git pull <远程主机名> <远程分支名>:<本地分支名> |
上面命令表示,取回origin/next
分支,再与当前分支合并。实质上,这等同于先做git fetch
,再做git merge
。
1 | git fetch origin |
在某些场合,Git 会自动在本地分支与远程分支之间,建立一种追踪关系(tracking)。比如,在git clone
的时候,所有本地分支默认与远程主机的同名分支建立追踪关系,也就是说,本地的master
分支自动追踪origin/master
分支
Git 也允许手动建立追踪关系
1 | 指定`master`分支追踪`origin/next`分支 |
如果当前分支与远程分支存在追踪关系,git pull
就可以省略远程分支名。
1 | git pull origin |
如果当前分支只有一个追踪分支,连远程主机名都可以省略。
1 | 自动与唯一一个追踪分支进行合并。 |
如果合并需要采用 rebase 模式,可以使用--rebase
选项。
1 | git pull --rebase <远程主机名> <远程分支名>:<本地分支名> |
如果远程主机删除了某个分支,默认情况下,git pull
不会再拉取远程分支的时候,删除对应的本地分支。这是为了防止,由于其他人操作了远程主机,导致git pull
不知不觉删除了本地分支。
但是,你可以改变这个行为,加上参数-p
就会在本地删除远程已经删除的分支。
1 | git pull -p |
git push
git push
命令用于将本地分支的更新推送到远程主机。它的格式与git pull
命令相仿。
1 | git push <远程主机名> <本地分支名>:<远程分支名> |
1 | 存在追踪关系 |
如果省略本地分支名,则表示删除指定的远程分支,因为这等同于推送一个空的本地分支到远程分支。
1 | git push origin :master |
如果当前分支与远程分支之间存在追踪关系,则本地分支和远程分支都可以省略。
1 | git push origin |
如果当前分支只有一个追踪分支,那么主机名都可以省略。
1 | git push |
如果当前分支与多个主机存在追踪关系,则可以使用-u
选项指定一个默认主机。这样后面就可以不加任何参数使用git push
。
1 | git push -u origin master |
不带任何参数的git push
,默认只推送当前分支,这叫做 simple 方式。此外,还有一种 matching 方式,会推送所有有对应的远程分支的本地分支。Git 2.0 版本之前,默认采用 matching 方式,现在改为默认采用 simple 方式。如果要修改这个设置,可以采用git config
命令。
1 | git config --global push.default matching |
还有一种情况,就是不管是否存在对应的远程分支,将本地的所有分支都推送到远程主机,这时需要使用--all
选项。
1 | git push --all origin |
如果远程主机的版本比本地版本更新,推送时 Git 会报错,要求先本地做git pull
合并差异,再推送到远程主机。这时,如果你一定要推送,可以使用--force
选项。
1 | git push --force origin |
上面命令使用--force
选项,结果导致远程主机上更新的版本被覆盖!除非你很确定要这样做,否则应该尽量避免使用--force
选项。
最后,git push
不会推送标签(tag),除非使用--tags
选项。
1 | git push origin --tags |
Git 工作流程
- git flow
- github flow
- gitlab flow
功能驱动
上述三种工作流程,都采用功能驱动式开发,它指的是,需求是开发的起点,先有需求再有功能分支(feature branch)或者补丁分支(hotfix branch)。开发完成后,该分支就合并到主分支,然后被删除。
Git flow
特点
首先,项目存在两个长期分支:
- 主分支 master
- 开发分支 develop
前者用于存放对外发布的版本,任何时候在这个分支拿到的,都是稳定的发布版;后者用于日常开发,存放最新的开发版。
其次,项目存在三种短期分支:
- 功能分支 feature branch
- 补丁分支 hotfix branch
- 预发分支 release branch
一旦开发完成,他们就会被合并进 develop 或 master,然后被删除。
评价
Git flow 的优点是清晰可控,缺点是相对复杂,需要同时维护两个长期分支。大多数工具都将 master 当做默认分支,可是开发是在 develop 分支进行的,这导致经常要切换分支,非常烦人。
更大的问题在于,这个模式是基于“版本发布”的,目标是一段时间以后产出一个新版本。但是,很多网站项目是“持续发布”,代码一有改动,就部署一次。这时,master 分支 和 develop 分支的差别不大,没必要维护两个长期分支。
Github flow
流程
它只有一个长期分支,就是 master。
- 根据需求,从 master 拉出新分支,不区分功能分支或补丁分支
- 新分支开发完成后,或者需要讨论的时候,就向 master 发起一个
pull request
(简称 PR) pull request
既是一个通知,让别人注意到你的请求,又是一种对话机制,大家一起评审和讨论你的代码。对话过程中,你还可以不断提交代码。- 你的
pull request
被接受,合并进 master,重新部署后,原来你拉出来的那个分支就被删除(先部署再合并也可)
评价
Github flow 的最大优点就是简单,对于“持续发布”的产品,可以说是最合适的流程。
问题在于它的假设:master 分支的更新与产品的发布是一致的。也就是说,master 分支的最新代码,默认就是当前的线上代码。
可是,有些时候并非如此,代码合并进入 master 分支,并不代表它就能立刻发布。比如,苹果商店的 App 提交审核以后,等一段时间才能上架。这时,如果还有新的代码提交,master 分支就会与刚发布的版本不一致。另一个例子是,有些公司有发布窗口,只有指定时间才能发布,这也会导致线上版本落后于 master 分支。
上面这种情况,只有 master 一个主分支就不够用了。通常,你不得不在 master 分支以外,另外新建一个 production 分支跟踪线上版本。
Gitlab flow
上游优先
Gitlab flow 的最大原则叫做“上游优先”(upstream first),即只存在一个主分支 master,它是所有其他分支的“上游”。只有上游分支采纳的代码变化,才能应用到其他分支。
持续发布
对于“持续发布”的项目,它建议在 master 分支以外,再建立不同的环境分支。比如,“开发环境”的分支是 master,“预发环境”的分支是 pre-production,“生产环境”的分支是 production。
开发分支是预发分支的“上游”,预发分支又是生产分支的“上游”。代码的变化,必须由“上游”向“下游”发展。比如,生产环境出现了 bug,这时就要新建一个功能分支,先把它合并到 master,确认没有问题,再 cherry-pick 到 pre-production,这一步也没有问题,才进入 production。
只有紧急情况,才允许跳过上游,直接合并到下游分支。
版本发布
对于“版本发布”的项目,建议的做法是每一个稳定版本,都要从 master 分支拉出一个分支,比如 2-3-stable、2-4-stable 等等。以后,只有修补 bug,才允许将代码合并到这些分支,并且此时要更新小版本号。