本来地址:git干货系列:(五)多人协同工做之分支管理
博客地址:tengj.top/javascript
分支就是科幻电影里面的平行宇宙,当你正在电脑前努力学习Git
的时候,另外一个你正在另外一个平行宇宙里努力学习SVN
。若是两个平行宇宙互不干扰,那对如今的你也没啥影响。不过,在某个时间点,两个平行宇宙合并了,结果,你既学会了Git
又学会了SVN
!
java
为了真正理解 Git
处理分支的方式,咱们须要回顾一下Git
是如何保存数据的。
Git 保存的不是文件的变化或者差别,而是一系列不一样时刻的文件快照。在进行提交操做时,Git
会保存一个提交对象(commit object
)。知道了Git
保存数据的方式,咱们能够很天然的想到——该提交对象会包含一个指向暂存内容快照的指针。 但不只仅是这样,该提交对象还包含了做者的姓名和邮箱、提交时输入的信息以及指向它的父对象的指针。首次提交产生的提交对象没有父对象,普通提交操做产生的提交对象有一个父对象,而由多个分支合并产生的提交对象有多个父对象。 git
Git
的分支,其实本质上仅仅是指向提交对象的可变指针。 Git
的默认分支名字是 master
。 在屡次提交操做以后,你其实已经有一个指向最后那个提交对象的 master
分支。 它会在每次的提交操做中自动向前移动。 vim
Git
的 “master” 分支并非一个特殊分支。它就跟其它分支彻底没有区别。 之因此几乎每个仓库> 都有 master 分支,是由于git init
命令默认建立它,而且大多数人都懒得去改动它。安全
分支在实际中有什么用呢?假设你准备开发一个新功能,可是须要两周才能完成,第一周你写了50%的代码,若是马上提交,因为代码还没写完,不完整的代码库会致使别人不能干活了。若是等代码所有写完再一次提交,又存在丢失天天进度的巨大风险。
如今有了分支,就不用怕了。你建立了一个属于你本身的分支,别人看不到,还继续在原来的分支上正常工做,而你在本身的分支上干活,想提交就提交,直到开发完毕后,再一次性合并到原来的分支上,这样,既安全,又不影响别人工做。
其余版本控制系统如SVN等都有分支管理,可是用过以后你会发现,这些版本控制系统建立和切换分支比蜗牛还慢,简直让人没法忍受,结果分支功能成了摆设,你们都不去用。
但Git
的分支是不同凡响的,不管建立、切换和删除分支,Git
在1秒钟以内就能完成!不管你的版本库是1个文件仍是1万个文件。bash
Git
是怎么建立新分支的呢? 很简单,它只是为你建立了一个能够移动的新的指针。 好比,建立一个 testing
分支, 你须要使用 git branch
命令: 服务器
$ git branch testing复制代码
这会在当前所在的提交对象上建立一个指针。 学习
那么,Git
又是怎么知道当前在哪个分支上呢? 也很简单,它有一个名为 HEAD
的特殊指针。 请注意它和许多其它版本控制系统(如 Subversion 或 CVS)里的 HEAD
概念彻底不一样。 在 Git
中,它是一个指针,指向当前所在的本地分支(译注:将 HEAD
想象为当前分支的别名)。 在本例中,你仍然在master
分支上。 由于 git branch
命令仅仅 建立 一个新分支,并不会自动切换到新分支中去。 fetch
你能够简单地使用 git log
命令查看各个分支当前所指的对象。 提供这一功能的参数是 --decorate
。ui
$ git log --oneline --decorate
f30ab (HEAD, master, testing) add feature #32 - ability to add new
34ac2 fixed bug #1328 - stack overflow under certain conditions
98ca9 initial commit of my project复制代码
正如你所见,当前 “master” 和 “testing” 分支均指向校验和以 f30ab
开头的提交对象。
要切换到一个已存在的分支,你须要使用git checkout
命令。 咱们如今切换到新建立的 testing
分支去:
$ git checkout testing复制代码
这样 HEAD
就指向 testing
分支了。
上面的建立分支和切换分支命令能够合起来用下面这个命令来替代。
$ git checkout -b testing复制代码
那么,这样的实现方式会给咱们带来什么好处呢? 如今不妨再提交一次:
$ vim test.rb
$ git commit -a -m 'made a change'复制代码
testing
分支向前移动了,可是
master
分支却没有,它仍然指向运行
git checkout
时所指的对象。 这就有意思了,如今咱们切换回
master
分支看看:
$ git checkout master复制代码
master
分支,二是将工做目录恢复成
master
分支所指向的快照内容。 也就是说,你如今作修改的话,项目将始于一个较旧的版本。 本质上来说,这就是忽略
testing
分支所作的修改,以便于向另外一个方向进行开发。
git branch
命令查看当前分支,注意前面带
*
的表示当前分支
Note
分支切换会改变你工做目录中的文件
在切换分支时,必定要注意你工做目录里的文件会被改变。 若是是切换到一个较旧的分支,你的工做目> 录会恢复到该分支最后一次提交时的样子。 若是Git
不能干净利落地完成这个任务,它将禁止切换分支。
假如咱们在testing
上的工做完成了,就能够把testing
合并到master
上。Git
怎么合并呢?最简单的方法,就是直接把master
指向testing
的当前提交,就完成了合并,这里你须要使用git merge
命令
$ git merge testing
Updating 64ba18a..760118b
Fast-forward
hello.txt | 1 +
1 file changed, 1 insertion(+)
create mode 100644 hello.txt复制代码
git merge
命令用于合并指定分支到当前分支。合并后,再查看内容,就能够看到,和testing
分支的最新提交是彻底同样的。
注意到上面的Fast-forward
信息,Git
告诉咱们,此次合并是“快进模式”,也就是直接把master
指向testing
的当前提交,因此合并速度很是快。
固然,也不是每次合并都能Fast-forward
,咱们后面会讲其余方式的合并。
合并完分支后,甚至能够删除dev
分支。删除dev
分支就是把dev
指针给删掉,删掉后,咱们就剩下了一条master
分支,这里须要使用git branch -d
命令来删除分支
$ git branch -d testing
Deleted branch testing (was 760118b).复制代码
人生不如意之事十之八九,合并分支每每也不是一路顺风的。
准备新的dev
分支,继续咱们的新分支开发:
$ git checkout -b dev
Switched to a new branch 'dev'复制代码
修改README.md
内容,添加同样内容"day day up~",在dev
分支上提交:
$ git commit -am "one commit"
[dev 6a6a08e] one commit
1 file changed, 1 insertion(+)复制代码
切换到master
分支:
$ git checkout master
Switched to branch 'master'
Your branch is up-to-date with 'origin/master'.复制代码
Git
还会自动提示咱们当前master
分支比远程的master
分支要超前1个提交。
在master
分支上把README.md
文件的最后改成 good good study
,而后提价
$ git commit -am "two commit"
[master 75d6f25] two commit
1 file changed, 1 insertion(+)复制代码
如今,master
分支和dev
分支各自都分别有新的提交,变成了这样:
$ git merge dev
Auto-merging README.md
CONFLICT (content): Merge conflict in README.md
Automatic merge failed; fix conflicts and then commit the result.复制代码
果真冲突了!Git告诉咱们, README.md文件存在冲突,必须手动解决冲突后再提交。git status
也能够告诉咱们冲突的文件:
$ git status
On branch master
Your branch is ahead of 'origin/master' by 1 commit.
(use "git push" to publish your local commits)
You have unmerged paths.
(fix conflicts and run "git commit")
Unmerged paths:
(use "git add <file>..." to mark resolution)
both modified: README.md
no changes added to commit (use "git add" and/or "git commit -a")复制代码
咱们能够直接查看README.md
的内容:
$ cat README.md
#gitLearn
<<<<<<< HEAD
good good study
=======
day day up
>>>>>>> dev复制代码
Git用<<<<<<<
,=======
,>>>>>>>
标记出不一样分支的内容,咱们修改以下后保存:
#gitLearn
good good study
day day up复制代码
再提交:
$ git commit -am 'merge commit'
[master 9a4d00b] merge commit复制代码
如今,master
分支和dev
分支变成了下图所示:
用带参数的git log
也能够看到分支的合并状况:
$ git log --graph --pretty=oneline --abbrev-commit
* 9a4d00b merge commit
|\
| * 6a6a08e one commit
* | 75d6f25 two commit
|/
* ae06dcf 123
* 760118b test
* 64ba18a test
|\
| * 4392848 Accept Merge Request #1 test : (dev -> master)
| |\
| | * a430c4b update README.md
| |/
| * 88ec6d7 Initial commit
* 32d11c8 update README.md
* 8d5acc1 new file README
* e02f115 Initial commit复制代码
最后,删除feature1
分支:
$ git branch -d dev
Deleted branch dev (was 6a6a08e).复制代码
一般,合并分支时,若是可能,Git会用Fast forward
模式,但这种模式下,删除分支后,会丢掉分支信息。
若是要强制禁用Fast forward
模式,Git
就会在merge
时生成一个新的commit
,这样,从分支历史上就能够看出分支信息。
下面咱们实战一下--no-ff
方式的git merge
:
首先,仍然建立并切换dev
分支:
$ git checkout -b dev
Switched to a new branch 'dev'复制代码
修改README.md文件,并提交一个新的commit:
$ git commit -am 'submit'
[dev fee6025] submit
1 file changed, 1 insertion(+)复制代码
如今,咱们切换回master
:
$ git checkout master
Switched to branch 'master'复制代码
目前来讲流程图是这样:
准备合并dev
分支,请注意--no-ff
参数,表示禁用Fast forward
:
$ git merge --no-ff -m "merge with no-ff" dev
Merge made by the 'recursive' strategy.
README.md | 1 +
1 file changed, 1 insertion(+)复制代码
由于本次合并要建立一个新的commit,因此加上-m
参数,把commit描述写进去。
合并后,咱们用git log
看看分支历史:
$ git log --graph --pretty=oneline --abbrev-commit
* b98f802 merge with no-ff
|\
| * fee6025 submit
|/
* 9a4d00b merge commit
...复制代码
能够看到,不使用Fast forward
模式,merge后就像这样:
实际公司开发的时候通常3个分支就能够了:
首先,master
分支应该是很是稳定的,也就是仅用来发布新版本,平时不能在上面干活;
干活都在dev
分支上,也就是说,dev
分支是不稳定的,到某个时候,好比1.0版本发布时,再把dev
分支合并到master
上,在master
分支发布1.0版本,你和你的小伙伴们每一个人都在dev
分支上干活,每一个人都有本身的分支,时不时地往dev
分支上合并就能够了;bug
分支用来处理平常bug,搞定后合到dev分支便可;
假设远程公共仓库,有一个master
和一个dev
分支,进行多人协做开发时候(每一个人的公钥必须加入到远程帐号下,不然没法push
), 每一个人都应该clone
一份到本地。 可是clone
的只是master
,若是远程的master
和dev
同样,不要紧;若是不一致,则须要clone
出dev
分支 git checkout -b dev origin/dev
以后每一个人在本地的dev
分支上独自开发(最好不要在mast
上开发), 开发完成以后push
到远程dev
, git push origin dev
。 以后审核人再肯定是否合并dev
到master
。
当你从远程仓库克隆时,实际上Git自动把本地的master
分支和远程的master
分支对应起来了,而且,远程仓库的默认名称是origin
。
要查看远程库的信息,用git remote
:
$ git remote
origin复制代码
或者,用git remote -v
显示更详细的信息:
$ git remote -v
origin git@git.coding.net:tengj/gitLearn.git (fetch)
origin git@git.coding.net:tengj/gitLearn.git (push)复制代码
上面显示了能够抓取和推送的origin
的地址。若是没有推送权限,就看不到push的地址。
推送分支,就是把该分支上的全部本地提交推送到远程库。推送时,要指定本地分支,这样,Git
就会把该分支推送到远程库对应的远程分支上:
$ git push origin master复制代码
若是要推送其余分支,好比dev
,就改为:
$ git push origin dev复制代码
多人协做时,你们都会往master
和dev
分支上推送各自的修改。
如今,模拟一个你的小伙伴,能够在另外一台电脑(注意要把SSH Key
添加到GitHub
)或者同一台电脑的另外一个目录下克隆:
$ git clone git@git.coding.net:tengj/gitStudy.git
Cloning into 'gitStudy'...
remote: Counting objects: 3, done.
remote: Total 3 (delta 0), reused 0 (delta 0)
Receiving objects: 100% (3/3), done.
Checking connectivity... done.复制代码
当你的小伙伴从远程库clone时,默认状况下,你的小伙伴只能看到本地的master
分支。不信能够用git branch
命令看看:
$ git branch
* master复制代码
如今,你的小伙伴要在dev
分支上开发,就必须建立远程origin
的dev
分支到本地,因而他用这个命令建立本地dev
分支(程分支dev要先建立)。
$ git checkout -b dev
git复制代码
建立dev分以后,先同步远程服务器上的数据到本地
$ git fetch origin
From git.coding.net:tengj/gitStudy
* [new branch] dev -> origin/dev复制代码
如今,他就能够在dev
上继续修改,而后,时不时地把dev
分支push
到远程:
$ git commit -am 'test'
[dev c120ad6] test
1 file changed, 1 insertion(+)
$ git push origin dev
Counting objects: 3, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (2/2), done.
Writing objects: 100% (3/3), 262 bytes | 0 bytes/s, done.
Total 3 (delta 0), reused 0 (delta 0)
To git@git.coding.net:tengj/gitStudy.git
65c05aa..c120ad6 dev -> dev复制代码
你的小伙伴已经向origin/dev
分支推送了他的提交,而碰巧你也对一样的文件做了修改,并试图推送:
$ git push origin dev
To git@git.coding.net:tengj/gitStudy.git
! [rejected] dev -> dev (fetch first)
error: failed to push some refs to 'git@git.coding.net:tengj/gitStudy.git'
hint: Updates were rejected because the remote contains work that you do
hint: not have locally. This is usually caused by another repository pushing
hint: to the same ref. You may want to first 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已经提示咱们,先用git pull
把最新的提交从origin/dev
抓下来,而后,在本地合并,解决冲突,再推送:
$ git pull origin dev
remote: Counting objects: 3, done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 3 (delta 0), reused 0 (delta 0)
Unpacking objects: 100% (3/3), done.
From git.coding.net:tengj/gitStudy
* branch dev -> FETCH_HEAD
b7b87f4..f636337 dev -> origin/dev
Auto-merging a.txt
CONFLICT (content): Merge conflict in a.txt
Automatic merge failed; fix conflicts and then commit the result.复制代码
所以,多人协做的工做模式一般是这样:
git push origin branch-name
推送本身的修改;git pull
试图合并;git push origin branch-name
推送就能成功!若是git pull
提示“no tracking information”,则说明本地分支和远程分支的连接关系没有建立,用命令git branch --set-upstream-to branch-name origin/branch-name
。
这就是多人协做的工做模式,一旦熟悉了,就很是简单。
到此,Git
分支管理就学完了,整理一下所学的命令,大致以下:
git branch 查看当前分支
git branch -v 查看每个分支的最后一次提交
git branch -a 查看本地和远程分支的状况
git branch --merged 查看已经与当前分支合并的分支
git branch --no-merged 查看已经与当前分支未合并的分支
git branch -r 查看远程分支
git branch dev 建立分支 dev
git checkout dev 切换到分支dev
git checkout -b dev 建立并切换分支dev
git merge dev 名称为dev的分支与当前分支合并
git branch -d dev 删除分支dev复制代码
一直以为本身写的不是技术,而是情怀,一篇篇文章是本身这一路走来的痕迹。靠专业技能的成功是最具可复制性的,但愿个人这条路能让你少走弯路,但愿我能帮你抹去知识的蒙尘,但愿我能帮你理清知识的脉络,但愿将来技术之巅上有你也有我。
更多干货内容,尽在嘟爷java超神学堂(javaLearn),您不扫一下么