如果在公司内部只有一个共用的 Git 远端储存库,大家都有存取权限的情况下,可能会遇到一些协同作业上的问题,那就是不同人彼此之间的程式码互相干扰的情况。例如你在团队开发的过程中,被指派负责开发新功能,但同时间其他同事负责修正目前上线的程式错误,如果两人共用同一个分支 (例如 master
分支),那麽在版控的过程中就很容易发生衝突的情况,这时你应该要善加利用分支将不同用途的原始码分别进行版本管理。
我在 GitHub 上建立了一个 sandbox-multi-branch
专案,并直接在 GitHub 上建立一个 Initial commit 版本,用做本篇文章的远端储存库。但这次我改用 git@github.com:doggy8088/sandbox-multi-branch.git
这个网址,透过 SSH 通讯协定来存取我的远端储存库,好让我可以不用每次执行远端储存库的操作时都要输入帐号密码。
我们执行以下指令将专案複製回来:
C:\>git clone git@github.com:doggy8088/sandbox-multi-branch.git
Cloning into 'sandbox-multi-branch'...
Receiving objecg objects: 3, done.Receiving objects: 33% (1/3)
Receiving objects: 100%0), reused 0 (delta 0)ts: 66% (2/3)
Receiving objects: 100% (3/3), done.
C:\>cd sandbox-multi-branch
C:\sandbox-multi-branch>git log
commit 6eeee883275e3d5e0281767aca4f456d952fa682
Author: Will 保哥 <xxx@gmail.com>
Date: Sun Oct 27 07:13:50 2013 -0700
Initial commit
C:\sandbox-multi-branch>git branch -a
* master
remotes/origin/HEAD -> origin/master
remotes/origin/master
此时我们的 .git\config
内容如下:
[core]
repositoryformatversion = 0
filemode = false
bare = false
logallrefupdates = true
symlinks = false
ignorecase = true
hideDotFiles = dotGitOnly
[remote "origin"]
url = git@github.com:doggy8088/sandbox-multi-branch.git
fetch = +refs/heads/*:refs/remotes/origin/*
[branch "master"]
remote = origin
merge = refs/heads/master
我们知道在建立好一个新的 Git 储存库时,预设都会有一个 master
分支。在实务上,这个分支通常用来当作目前系统的「稳定版本」,也就是这个版本必须是乾淨且高品质的原始码版本。所以,我们会要求所有人都不要用这个分支来建立任何版本,真正要建立版本时,一定会透过「合併」的方式来进行操作,以确保版本能够更容易被追踪。
进入开发阶段时,我们通常会再从 master
分支建立起另一个 develop
分支,用来作为「开发分支」,也就是所有人都会在这个分支上进行开发,但这个时候或许会产生一些衝突的情形,因为大家都在同一个分支上进行版本控管。不过这种用法跟以往我们用 Subversion 的时候是比较类似的,所以使用上的观念通常差不多。本文稍后就会介绍一些分支开发的练习,但我们现在就可以先建立一个 develop
分支起来。
C:\sandbox-multi-branch>git branch
* master
C:\sandbox-multi-branch>git checkout -b develop
Switched to a new branch 'develop'
C:\sandbox-multi-branch>git branch
* develop
master
开发过程中,有时候我们也会因为需求变更,而被指派开发一些新功能,但这些新功能可能变动性还很大,甚至只是想进行 PoC 验证而开发的小功能。这些小功能只是测试用途,你不想因为开发这些测试功能而影响到大家的开发作业,所以这时我们会选择再建立起一个「新功能分支」,专门用来存放这些新增功能的程式码版本。这个测试用的「功能分支」,通常会建立在 develop
之上,所以我们会再从 develop
分支来建立另一个分支。但这个分支的名称,实务上通常会取名为 feature/[branch_name]
,例如:feature/aspnet_identity
。
C:\sandbox-multi-branch>git branch
* develop
master
C:\sandbox-multi-branch>git checkout -b feature/aspnet_identity
Switched to a new branch 'feature/aspnet_identity'
C:\sandbox-multi-branch>git branch
develop
* feature/aspnet_identity
master
如果你发现在开发的过程中,「正式机」 (生产环境) 的系统出现了一个严重错误,但在「开发分支」裡又包含一些尚未完成的功能,这时你可能会从 master
分支紧急建立一个「修正分支」,通常的命名为 hotfix/[branch_name]
,例如:hotfix/bugs_in_membership
。
C:\sandbox-multi-branch>git branch
develop
* feature/aspnet_identity
master
C:\sandbox-multi-branch>git checkout master
Switched to branch 'master'
C:\sandbox-multi-branch>git branch
develop
feature/aspnet_identity
* master
C:\sandbox-multi-branch>git checkout -b hotfix/bugs_in_membership
Switched to a new branch 'hotfix/bugs_in_membership'
C:\sandbox-multi-branch>git branch
develop
feature/aspnet_identity
* hotfix/bugs_in_membership
master
如果你发现目前的 master
分支趋于稳定版本,那麽你可能会想替目前的 master
分支建立起一个「标籤物件」或称「标示标籤」(annotated tag),那麽你可以先切换到 master
分支后输入 git tag 1.0.0-beta1 -a -m "V1.0.0-beta1 created"
即可建立一个名为 1.0.0-beta
的标示标籤,并透过 -m
赋予标籤一个说明讯息。
C:\sandbox-multi-branch>git branch
develop
feature/aspnet_identity
* hotfix/bugs_in_membership
master
C:\sandbox-multi-branch>git checkout master
Switched to branch 'master'
C:\sandbox-multi-branch>git tag 1.0.0-beta1 -a -m "V1.0.0-beta1 created"
C:\sandbox-multi-branch>git tag
1.0.0-beta1
以上就是使用 Git 的过程中常见的命名规则与版控流程。
目前为止我们建立了好几个分支与标籤,用 SourceTree 来看,目前还看不出分支的版本线图,毕竟我们还没有建立任何版本,但该有的分支已经被成功建立,如下图示:
不过,这些分支都仅储存在本地储存库中,团队中所有其他人都无法得到你建立的这些分支,如果要将这些分支的参照名称推送到远端储存库,可以使用 git push --all
这个指令。
C:\sandbox-multi-branch>git push --all
Total 0 (delta 0), reused 0 (delta 0)
To git@github.com:doggy8088/sandbox-multi-branch.git
* [new branch] develop -> develop
* [new branch] feature/aspnet_identity -> feature/aspnet_identity
* [new branch] hotfix/bugs_in_membership -> hotfix/bugs_in_membership
不过如果只下达 --all
参数是不够的,可能还要加上 --tags
参数,才能将标示标籤也一併推送到远端储存库。
C:\sandbox-multi-branch>git push --tags
Counting objects: 1, done.
Writing objects: 100% (1/1), 159 bytes | 0 bytes/s, done.
Total 1 (delta 0), reused 0 (delta 0)
To git@github.com:doggy8088/sandbox-multi-branch.git
* [new tag] 1.0.0-beta1 -> 1.0.0-beta1
这个时候,所有物件与参照名称都已经储存在远端储存库了。我们连到 GitHub 就能看到这些物件已经可以被浏览到:
如果切换到 Tags 页籤的话,也可以看到标籤物件也被送上来了:
这个时候大家就能够透过 git fetch --all --tags
将所有物件取回,包含所有物件参照与标籤参照。
我们建立起另一个工作目录,模拟其他使用者取回资料的情况:
C:\>git clone git@github.com:doggy8088/sandbox-multi-branch.git sandbox-multi-branch-user2
Cloning into 'sandbox-multi-branch-user2'...
remote: Counting objects: 4, done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 4 (delta 0), reused 1 (delta 0)
Receiving objects: 100% (4/4), done.
C:\>cd sandbox-multi-branch-user2
C:\sandbox-multi-branch-user2>git fetch --all --tags
Fetching origin
取回物件后,用 SourceTree 查看储存库的状态如下:
现在开始,团队所有成员都拥有了预先定义好的「Git 储存库范本」,大家就能各就各位,开发自己需要开发的功能。或许会有两个人在 develop
分支上进行开发,或许会有一个人被指派 hotfix/bugs_in_membership
分支进行修复任务,诸如此类的,等分支完成开发后,再将变更推送到远端储存库裡。
眼尖的你可能会发现,这个 User2
的本地分支只有 master
而已,跟我们原本建立的那个工作目录有些不一样。之前在【第 25 天:使用 GitHub 远端储存库 - 观念篇】文章中不是提到说「把这些「本地追踪分支」视为是一种「唯读」的分支」吗?没有本地分支要怎样进行呢?
关于这一点,各位也不用担心,Git 早就帮我们想好了。假设你现在被赋予的任务是去开发 hotfix/bugs_in_membership
分支,并负责把变更错误修正,你可以直接执行 git checkout hotfix/bugs_in_membership
将这个「本地追踪分支」给取出 (checkout)。
C:\sandbox-multi-branch-user2>git branch -a
* master
remotes/origin/HEAD -> origin/master
remotes/origin/develop
remotes/origin/feature/aspnet_identity
remotes/origin/hotfix/bugs_in_membership
remotes/origin/master
C:\sandbox-multi-branch-user2>git checkout hotfix/bugs_in_membership
Branch hotfix/bugs_in_membership set up to track remote branch hotfix/bugs_in_me
mbership from origin.
Switched to a new branch 'hotfix/bugs_in_membership'
C:\sandbox-multi-branch-user2>git branch -a
* hotfix/bugs_in_membership
master
remotes/origin/HEAD -> origin/master
remotes/origin/develop
remotes/origin/feature/aspnet_identity
remotes/origin/hotfix/bugs_in_membership
remotes/origin/master
在取出 hotfix/bugs_in_membership
这个「本地追踪分支」后,Git 会动帮你建立起一个同名的「本地分支」,所以你根本不用担心有没有本地分支的情形。
这时我们模拟在 hotfix/bugs_in_membership
这个「本地分支」建立一个版本:
C:\sandbox-multi-branch-user2>git status
# On branch hotfix/bugs_in_membership
nothing to commit, working directory clean
C:\sandbox-multi-branch-user2>echo %date% %time% > a.txt
C:\sandbox-multi-branch-user2>git add .
C:\sandbox-multi-branch-user2>git commit -m "Add a.txt"
[hotfix/bugs_in_membership 250e907] Add a.txt
1 file changed, 1 insertion(+)
create mode 100644 a.txt
目前的版本线图如下:
接著如果你想将变更推送到远端,只要下达 git push origin hotfix/bugs_in_membership
即可将变更推送回去:
C:\sandbox-multi-branch-user2>git push origin hotfix/bugs_in_membership
Counting objects: 4, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (2/2), done.
Writing objects: 100% (3/3), 294 bytes | 0 bytes/s, done.
Total 3 (delta 0), reused 0 (delta 0)
To git@github.com:doggy8088/sandbox-multi-branch.git
6eeee88..250e907 hotfix/bugs_in_membership -> hotfix/bugs_in_membership
不过目前为止,你所推送回去的,只有 hotfix/bugs_in_membership
这个分支的版本而已,你并没有将变更「合併」回 master
分支。这样操作所代表的意思是,你将变更放上远端储存库,目的是为了将变更可以让其他人看到,也可以取回继续修改,就跟昨天【第 26 天:多人在同一个远端储存库中进行版控】文章中讲的版控流程一样。
如果你想合併回去,可以先切换至 master
分支,再去合併 hotfix/bugs_in_membership
分支的变更,最后在推送到远端储存库。如下指令范例:
C:\sandbox-multi-branch-user2>git branch -a
* hotfix/bugs_in_membership
master
remotes/origin/HEAD -> origin/master
remotes/origin/develop
remotes/origin/feature/aspnet_identity
remotes/origin/hotfix/bugs_in_membership
remotes/origin/master
C:\sandbox-multi-branch-user2>git checkout master
Switched to branch 'master'
C:\sandbox-multi-branch-user2>git merge hotfix/bugs_in_membership
Updating 6eeee88..250e907
Fast-forward
a.txt | 1 +
1 file changed, 1 insertion(+)
create mode 100644 a.txt
C:\sandbox-multi-branch-user2>git push
Total 0 (delta 0), reused 0 (delta 0)
To git@github.com:doggy8088/sandbox-multi-branch.git
6eeee88..250e907 master -> master
如此一来,master
的分支内容就成功被更新,并且推送到远端储存库了。
想当然尔,其他的分支也都是用类似的方式运作著。
今天更进一步介绍了 Git 更接近实务面的版控流程,让大家透过不同的分支进行开发,彼此之间做一个有效区隔,然后还能在同一个远端储存库中进行版控,同时享受了集中式版本控管的特性,以及分散式版本控管的弹性,非常优秀不是吗! ^_^
我重新整理一下本日学到的 Git 指令与参数: