Git使用总结五:分支管理
前些时间,公司产品经理针对司机端提出一个微信及支付宝二维码扫码支付需求,需求不急,但也是刚性任务,于是我在会后便火急火燎开干,期间碰到一些问题获取了一些经验,想分享给各位。
第一个问题,期间,或许因为公司反复修网造成GitLab地址变动,管理GitLab的后台并没有多余的时间来维护,所以短时间内,并不能将本地的修改 push
到远程。这种情况下,我将每次的修改 commit
到本地,等GitLab好了再 push
,对此我深刻感受到Git的好处。
第二个问题,在公司我负责iOS移动端的独立开发与维护。由于是独立开发,使用Git时,我一般在 dev
上 fix bug
或者改需求。此次,我需求完成一半,测试那边给出反馈,我放下手头需求,紧急 fix bug
,commit
到本地后,在无法 push
到远程的情况下,我需要紧急提交一个新版本。问题来了,由于GitLab未修复,我无法clone
上个版本到本地来fix bug
,又因为我dev分支上同时fix bug
和迭代需求,本地也不是上一个版本。
创建与合并分支
在Git里,每次提交,Git把提交串成一个分支,这个分支是主分支,即 master
分支。HEAD
不是指向提交,而是指向 master
,master
才指向提交,所以,HEAD
指向的是当前分支。
Git创建一个 dev
分支时很快,因为除了需要增加一个 dev
指针,改变 HEAD
的指向,工作区的文件没有变。
Git合并 dev
分支到 master
分支也很快,在 dev
上完成工作后,直接把 master
指向 dev
当前的提交,就完成了合并。合并过程中,指针指向改变,工作区内容同样没有变。
- 创建
dev
分支,然后切换到dev
分支
git checkout -b dev
- 创建分支
git branch <name>
- 切换分支
git checkout <name>
- 用
git branch
查看当前分支
$ git branch
* dev
master
-
git merge
命令用于合并指定分支到当前分支
git merge <name>
- 删除分支
git branch -d <name>
解决冲突
- 创建并切换
feature
分支
$ git checkout -b feature
Switched to a new branch feature
- 修改工作区内容后,在
feature
分支上提交修改并且将暂存区的修改commit
到当前分支
$ git add readme.txt
$ git commit -m "and simple"
[feature 75a857c] and simple
1 file changed, 1 insertion(+), 1 deletion(-)
- 切换到
master
分支,系统将会提示当前master
分支彼远程的master
分支要超前一个提交
$ git checkout master
Switched to branch 'master'
Your branch is ahead of 'origin/master' by 1 commit
- 在
master
分支上把工作区内容进行修改,并且提交
$ git add readme.txt
$ git commit -m "& simple"
[master 400b400] & simple
1 file changed, 1 insertion(+), 1 deletion(-)
- 此刻在master分支和feature分支都要提交的情况下,Git无法进行快速合并,如果试图把各自的修改合并,将会造成冲突。
$ git merge feature
Auto-merging readme.txt
CONFLICT (content): Merge conflict in readme.txt
Automatic merge failed; fix conflicts and then commit the result.
- Git将会提示,readme.txt文件存在冲突,必须手动解决冲突后再提交,我们一般使用
git status
了解我们冲突的文件。
$ git status
# On branch master
# Your branch is ahead of 'origin/master' by 2 commits.
#
# Unmerged paths:
# (use "git add/rm <file>..." as appropriate to mark resolution)
#
# both modified: readme.txt
#
no changes added to commit (use "git add" and/or "git commit -a")
-
直接查看readme.txt文件夹下内容,Git用
<<<<<<<
,=======
,>>>>>>>
标记出不同分支的内容,我们修改文件夹下内容使feature
分支和master
分支保持一致后,再提交。 -
使用带参数的
git log
也可以看到分支的合并情况
$ git log --graph --pretty=oneline --abbrev-commit
* 59bc1cb conflict fixed
|\
| * 75a857c AND simple
* | 400b400 & simple
|/
* fec145a branch test
...
- 最后删除分支
$ git branch -d feature
Deleted branch feature (was 75a857c).
- 所以说,当Git无法自动合并分支时,就必须首先解决冲突,解决冲突后,再提交,合并完成。使用
git log --graph
命令可以查看分支合并图
分支管理策略
在解决冲突时,我们提到了快速合并,通常,我们在合并分支时,Git会使用 Fast forward
模式,在 Fast forward
模式下,删除分支后,会丢失分支信息。
所以,我们会强制禁用 Fast forward
模式,Git会在 merge
时生成一个新的 commit
,这样我们可以在分支历史上查看分支信息。
--no-ff方式的git merge
- 首先,创建并切换
dev
分支
$ git checkout -b dev
Switched to a new branch dev
- 修改工作区readme.txt文件,并提交一个新的
commit
$ git add readme.txt
$ git commit -m "add merge"
[dev 6224937] add merge
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.txt | 1 +
1 file changed, 1 insertion(+)
- 合并后我们使用
git log
查看分支历史
$ git log --graph --pretty=oneline --abbrev-commit
* 7825a50 merge with no-ff
|\
| * 6224937 add merge
|/
* 59bc1cb conflict fixed
...
分支策略
其实,我们在实际开发中,应该按照几个基本原则进行分支管理:
-
首先,
master
分支应该是最稳定的,也就是说,我们平时不能在master
上干活,master
分支仅用来发布版本使用。 -
如果要干活,我们应该创建并切换一个分支,这个分支就是
dev
分支。也就是说,dev
分支是不稳定的,平时我们各自在dev
分支上干活,每个人有自己的分支,不断往dev
分支上合并,等到版本发布时,再把dev
分支合并到master
上。
Bug分支
当前,正在 dev
上的工作还没提交,我们需要经紧急修复一个bug,很自然地,需要创建一个 bug
分支来修复这个bug。有人会有疑问,为什么不把 dev
上工作区的修改先进行提交,其实我们并不是不想提交,而是工作进行到一半无法提交。那么,Git为我们提供一个 stash
功能,先把当前工作现场“储藏”起来,等恢复现场后继续工作。
$ git stash
Saved working directory and index state WIP on dev: 6224937 add merge
HEAD is now at 6224937 add merge
-
git stash
后,我们用git status
查看工作区,工作区是干净的,我们可以放心创建分支修复bug。 -
确定在那个分支上修复bug,就在那个分支上创建bug分支。此次我们在
master
上修复,就从master
上创建临时分支。由于创建的bug
分支是临时分支,修复bug后,切换到master
分支,完成合并,最后删除bug
分支。 -
接着我们回到
dev
干活,使用git status
查看工作区状态,发现工作区是干净的,这是我们需要使用git stash list
查看存的工作现场。
$ git stash list
stash@{0}: WIP on dev: 6224937 add merge
-
使用
git stash apply
恢复,但是恢复后,stash内容不会删除,需要使用git stash drop
删除stash内容。 -
或者,使用
git stash pop
,恢复的同时把stash内容也删除了。
$ git stash pop
# On branch dev
# Changes to be committed:
# (use "git reset HEAD <file>..." to unstage)
#
# new file: hello.py
#
# Changes not staged for commit:
# (use "git add <file>..." to update what will be committed)
# (use "git checkout -- <file>..." to discard changes in working directory)
#
# modified: readme.txt
#
Dropped refs/stash@{0} (f624f8e5f082f2df2bed8a4e09c12fd2943bdd40)
- 使用
git stash pop
后,使用git stash list
查看,就看不到stash内容了。
Feature分支
一般情况下,添加一个新功能,需要创建一个 feature
分支,feature
分支作为临时开发新功能的分支最终要合并到 dev
分支,也就是说,我们添加一个新功能,并不是在 dev
上进行开发,而是要创建一个 feature
分支,而这个 feature
分支用于临时开发一个新功能,是一个临时分支。
-
其实,
feature
分支和bug
分支类似,创建切换,合并,然后删除。 -
取消新功能时,使用
git branch -d <name>
删除分支,但是会提示销毁失败,这时需要强行删除分支,使用命令git branch -D <name>
。
$ git branch -D feature-vulcan
Deleted branch feature-vulcan (was 756d4af).
- 所以,如果要丢弃一个没有被合并的分支,可以通过
git branch -D <name>
强行删除这个分支。
多人协作
真正意义上,Git的价值还是体现在团队协作作用上,而那些命令其实仅仅起到辅助协作的作用,如果是独立开发并不能深刻体会到这种协作的作用,但是如果进入纯技术公司团队,可能Git的这种协作价值才能真正的体现出来。
-
从远程仓库
clone
时,实际上Git自动把本地master
分支和远程master
分支对应起来了,并且,远程仓库默认的名称是origin
。 -
所以说,我们使用
git remote
查看远程仓库的信息
$ git remote
origin
- 或者,用
git remote -v
查看远程库详细信息
$ git remote -v
origin git@github.com:michaelliao/learngit.git (fetch)
origin git@github.com:michaelliao/learngit.git (push)
推送分支
-
master
分支在本地作为主分支,要时刻与远程仓库同步。 -
dev
分支是开发分支,团队所有成员都需要在dev
分支上工作,所以dev
分支同样需要与远程同步。 -
bug
分支作为临时分支,只在本地修复bug,没有必要推送到远程,可能一周推送一次。 -
feature
分支同样是临时分支,但是,feature
分支是否推送到分支,取决于是否团队协作,如果是独立开发,feature
分支没有必要推送到远程,如果协作开发,需要推送到远程,与远程保持同步。
抓取分支
- 从远程库
clone
时默认只能看到本地的master
分支,我们可以使用git stauts
查看。
$ git branch
* master
- 要在
dev
分支上开发,必须创建远程origin
的dev
分支到本地,可以使用git checkout -b dev origin/dev
命令创建本地dev
分支。
$ git checkout -b dev origin/dev
- 可以在本地创建的这个
dev
分支上修改,并且把修改commit
到dev
分支,然后把dev
分支push
到远程。
$ git commit -m "add /usr/bin/env"
[dev 291bea8] add /usr/bin/env
1 file changed, 1 insertion(+)
$ git push origin dev
Counting objects: 5, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (2/2), done.
Writing objects: 100% (3/3), 349 bytes, done.
Total 3 (delta 0), reused 0 (delta 0)
To git@github.com:michaelliao/learngit.git
fc38031..291bea8 dev -> dev
- 由于你已经向
origin/dev分支
推送了提交,而devB碰巧对同样的文件做了修改,并且试图推送,由于我的最新提交和devB试图推送的提交有冲突,所以,推送失败。
$ git add hello.py
$ git commit -m "add coding: utf-8"
[dev bd6ae48] add coding: utf-8
1 file changed, 1 insertion(+)
$ git push origin dev
To git@github.com:michaelliao/learngit.git
! [rejected] dev -> dev (non-fast-forward)
error: failed to push some refs to 'git@github.com:michaelliao/learngit.git'
hint: Updates were rejected because the tip of your current branch is behind
hint: 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.
- 这时,Git提醒,先用
git pull
把最新的提交从origin/dev
抓取下来,然后,在本地合并,先解决冲突,然后再推送到远程。
$ git pull
remote: Counting objects: 5, done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 3 (delta 0), reused 3 (delta 0)
Unpacking objects: 100% (3/3), done.
From github.com:michaelliao/learngit
fc38031..291bea8 dev -> origin/dev
There is no tracking information for the current branch.
Please specify which branch you want to merge with.
See git-pull(1) for details
git pull <remote> <branch>
If you wish to set tracking information for this branch you can do so with:
git branch --set-upstream dev origin/<branch>
-
git pull
失败,因为未指定本地dev
分支和远程origin/dev
分支链接,所以,设置dev
和远程origin/dev
的链接。
$ git branch --set-upstream dev origin/dev
Branch dev set up to track remote branch dev from origin.
-
git pull
成功后,git merge
合并存在冲突,先手动解决冲突,然后提交,最后push
到远程。
$ git commit -m "merge & fix hello.py"
[dev adca45d] merge & fix hello.py
$ git push origin dev
Counting objects: 10, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (5/5), done.
Writing objects: 100% (6/6), 747 bytes, done.
Total 6 (delta 0), reused 0 (delta 0)
To git@github.com:michaelliao/learngit.git
291bea8..adca45d dev -> dev
- 请记住,如果
git pull
提示“no tracking information“,则说明本地分支和远程分支的链接关系没有创建,使用命令git branch --set-upstream branch-name origin/branch-name
,以上就是多人协作的工作模式。