美文网首页
分支管理(第11篇)

分支管理(第11篇)

作者: 你好星期四 | 来源:发表于2016-03-28 19:18 被阅读127次

    分支概述

    Git里有一个很神奇的特性叫分支。说到分支,你脑海中的第一印象可能是火影忍者里的影分身术:复制多个本体。事实上,Git里的分支并不是为了复制多个本体,Git分支的存在更像是派一个探险员,走在自己的前面先尝试着做某件事情,或者去解决某个问题,等到这个探险员觉得没问题了,自己再跟上探险员的脚步。

    这样的事情在实际开发中经常存在。比如你在开发一个软件,需要增加一项新功能,你绝不能贸然的就去直接添加,因为新功能很可能会跟原本的代码有冲突,最后导致原本的代码也受到了影响。你希望的是,我可以在一个单独的地方做这件事,而不影响到原本的代码,等到这个新功能开发完成,再将你的代码合并到原来的代码中。

    在Git中,你可以使用分支来做这件事。

    事实上,之前我们所做的所有工作都在master分支上,master分支是主分支。之前我们为了学习Git,一直都在master分支上进行修改。而真实的开发中,我们不会这么做。因为真实的开发过程有很多的不确定性,在master分支上开发会很危险。我们通常都会新创建一个分支叫dev,它是develop的缩写,当然你也可以使用其他名字。我们的开发工作都是在这个dev分支上进行的。dev分支是开发的总分支,而如果你要开发一个新功能,或者修复一个Bug,你又需要在这个dev分支上创建其他的分支。你可能会跟很多人一起开发,每个人都在dev分支上分出一条自己的分支,每个人还可以在自己的分支中再创建子分支,然后当个人分支完成后,根据需要再往dev分支上合并,dev上完成一项任务后还可以往master分支合并。这样每个人都可以在自己的分支上做自己的事情,不会打扰到其他人,也不用担心自己未完成的代码会无意之中破坏master分支中的代码。

    创建分支

    首先我们创建一个test.txt的文件,在里面输入:

    # this is a test file
    

    然后我们将它提交:

    $ git add test.txt
    $ git commit -m "add a test file"
    

    接着,我们来创建一个dev分支。

    $ git branch dev
    

    这样,就生成了一个叫dev的分支。
    但别忘了,我们现在仍然在master分支,不信你看:

    $ git branch
      dev
    * master
    

    git branch会列出所有的分支,并在当前分支的前面加上一个*号。如果需要切换到dev分支,我们只需:

    $ git checkout dev
    

    还记得我们在撤销修改那一篇中使用过checkout吗?不过那里的的语法是:git checkout -- file,用于撤销工作区的修改。而切换分支命令没有 -- 这两个短横。

    如果你希望创建一个分支并直接切换到那个分支,还可以:

    $ git chekcout -b dev
    

    这样将会创建dev分支并直接切换到dev分支。

    好了,现在我们已经从master分支切换到了dev分支。master是老大,他现在让dev分支先去探探路,没办法,谁让他是老大呢!
    我们在dev分支中进行修改。在test.txt后面加上一行:

    # this is a test file
    first line
    

    接着我们进行提交:

    $ git add test.txt
    $ git commit -m "add first line  on dev"
    

    好了,我们在dev分支上给test.txt加上了一行。现在,我们切换回master分支:

    $ git checkout master
    

    当我们切换回master分支后,打开test.txt,发现:

    $ cat test.txt
    # this is a test file
    

    我们刚才添加的一行"first line"不见了。
    因为"first line"是在dev分支上提交的。

    合并分支

    现在,dev作为一个探险者已经出色的完成了它的探险任务,master需要跟它合并了。
    现在我们在master分支上,我们准备跟dev分支合并:

    $ git merge dev
    Updating 0fed6b6..38fb696
    Fast-forward
    test.txt | 1 +
    1 file changed, 1 insertion(+)
    

    合并完成了。
    Git告诉我们这次合并是Fast-forward(快速模式)。当然还有其他的模式。

    合并完成以后,你还可以删除dev分支:

    $ git branch -d dev
    

    如果你乐于尝试,你可能还会做一个实验:

    • 先在master分支下创建一个test.txt文本,并在里面写上
    # this is a test file
    

    然后git add ,git commit提交。

    • 再创建一个dev分支,并切换到dev分支:
    $ git checkout -b dev
    
    • 在dev分支下,给test.txt添加一行:
    # this is a test file
    first line
    
    • 前三步都跟刚才的一样,这第四步有所区别,我们不进行提交,我们在dev分支上只将test.txt添加到暂存区:
    $ git add test.txt
    

    记住,我们不在dev里commit提交。

    • 我们切换到master分支:
    $ git checkout master
    

    这时,你打开test.txt发现,你在dev分支上添加的“first line”仍然还在:

    # this is a test file
    first line
    

    你可能会觉得难以理解:“first line”不是在dev分支上添加的吗?怎么在master分支上还能看见?事实上,即使第4步你没有将test.txt添加到暂存区,仍然会有同样的结果。
    那是因为,你虽然是在dev分支上添加的“first line”,但是并没有在dev分支提交“first line”。
    现在有两种情况:
    第一种情况,如果此时你切换回dev分支,并将“first line”这一修改进行提交,然后再返回到master分支,这时,你就不会再看到“first line”了。
    第二种情况,如果你没有切换回dev分支进行提交,而是在master分支进行提交,那么当你返回到dev分支时,你将不会再看到“first line”了,因为Git认为“first line”这一修改是在master分支上进行的,而这一修改也确实是在master分支上提交的。

    看到这里,你是不是有点感觉:Git好像只在版本库上进行了分支上的区分,而没有将工作区和暂存区在分支上进行区分。

    你暂时可以这样理解。

    还有一件事情要注意:一旦两个分支的版本库不一样,这时你在某个分支上进行了修改而没有提交,那么Git是不允许你切换分支的,除非你强制切换。这时你刚刚在这个分支上做的修改也会被覆盖。(当然Git也允许你将当前的状态保存下来,这样不用提交就可以切换分支了。)

    合并时发生了冲突

    刚才我们在进行合并时很顺利,因为dev分支比master分支提前了一个提交,这样master分支在跟dev合并时只要把自己的指针指向dev就可以,并不会有冲突。

    但是如果在dev分支上修改了一些内容并进行了提交,然后返回master分支,也修改了一些内容并进行了提交,这时你再进行合并,就会有冲突。因为两个分支都进行了修改,那最后合并的结果到底听谁的?Git也不知道怎么办。这时需要你自己打开文件,手动的来解决文件中发生的冲突。

    • master分支中有一文件test.txt:
    # this is a test file
    
    • 创建dev分支并切换到dev分支:
    $ git checkout -b dev
    
    • 在dev分支中修改test.txt,添加一行:
    # this is a test file
    new line on branch dev
    
    • 在dev分支上提交这一修改:
    $ git add test.txt
    $ git commit -m "add new line on branch dev"
    
    • 然后切换到master分支:
    $ git checkout master
    

    可以查看在master分支里,“new line on branch dev”这一行并没有添加到test.txt中,因为这一修改是在dev分支提交的。

    # this is a test file
    
    • 在master分支上打开test.txt,添加一行新文本:
    # this is a test file
    another new line on branch master.
    
    • 在master分支上进行add 和commit:
    $ git add test.txt
    $ git commit -m "add another new line on branch master"
    
    • 然后我们进行合并:
    $ git merge dev
    Auto-merging test.txt
    CONFLICT (content): Merge conflict in test.txt
    Automatic merge failed; fix conflicts and then commit the result.
    

    发现报错了,Git告诉我们发生了冲突。这是我们打开test.txt发现Git已经自动为我们在发生冲突的地方标记了出来:

    $ cat test.txt
    # this is a test file
    <<<<<<< HEAD
    another new line on branch master
    =======
    new line one branch dev
    >>>>>>> dev
    

    Git使用<<<<<<< HEAD ======= >>>>>>> dev的方式为我们标记出了冲突的地方,我们将冲突的地方手动进行修改:

    # this is a test file
    anyway,this is a new line
    

    这时再进行提交:

    $ git add test.txt
    $ git commit -m "fix conflict"
    

    好了,冲突解决了。master分支上的test.txt已经变成你刚刚修改的那样了,变成了“anyway,this is a new line”。
    你可以用带参数的git log看到分支的合并情况:

    $ git log --graph --pretty=oneline --abbrev-commit
     *   3671953 fix conflict
    |\
    | * 74a391b add new line on branch dev
     * | 8a54ef7 add another new line on branch master
    |/
     * 901e873 add a test file
     * 38fb696 add first line on dev
     * 0fed6b6 add a test file
    
    • 现在我们回到dev分支,你查看一下test.txt会发现:
    # this is a test file
    new line on branch dev
    

    仍然还是“new line on branch dev”。这是因为合并发生在master分支上,跟dev分支无关。

    普通合并分支

    我们在上面说过,在不发生冲突的情况下,如果可能,Git会使用Fast forward模式合并。但这种方式合并,“合并”这件事情不会被记录下来, 今后你在使用git log查看提交历史的时候不会知道曾经进行过合并。如果你在以后想知道之前有没有进行合并,那么就需要在合并的时候强制禁止fast forward合并方式,将合并这件事本身也作为一次提交。

    $ git merge --no-ff -m "merge with no forward" dev 
    

    这样合并这件事就成为了一次提交,-m参数用于添加这次提交的说明。

    保存现场

    我们前面说过,如果两个分支的版本库不一样,那么你在一个分支上进行了修改并且没有提交,那你是不能切换到另一个分支的。

    但如果我在这个分支上的一个工作还没有完成,不适合提交。但我又要立刻切换到另一分支去修改一个Bug,时间紧迫,我又不能撤销我在当前分支上的修改,又不方便提交,还需要立即切换到另一分支去修复那个紧急的Bug……天哪,我到底该怎么办?

    事实上,你可以先把当前分支上的工作现场保存下来,然后你就可以切换到另一分支去修改那个Bug,等修复完Bug以后,你再切换回来,恢复工作现场。

    Git提供了这样一个命令来帮助你保存工作现场:

    $ git stash
    

    你现在使用git status查看一下工作区的状态,Git会告诉你工作区很干净,没有什么要提交的。那你就放心了。
    然后你就可以切换到另一分支了。在另一分支完成了工作,你再切换回这个分支。你需要恢复刚才保存的工作现场,但事实上,你可能保存了好几次工作现场,那么你就可以使用git stash list来查看有哪些工作现场:

    $ git stash list
    stash@{0}: WIP on dev: 893643d add new line on branch dev
    

    你现在需要恢复这个代号叫做stash@{0}的工作现场。
    你有两种方法:
    第一种,恢复工作现场,但不删除那个工作现场
    你可以使用git stash apply恢复,这个命令不会删除工作现场,当然你也可以使用git stash drop来手动的删除工作现场。

    $ git stash apply stash@{0}
    $ git stash drop
    

    第二种,在回复工作现场的同时,删除工作现场:

    $ git stash pop
    

    多人协作

    我们通常是把某一台电脑当做服务器,在这台服务器上安装了Git,然后其他人都把这台服务器作为远程仓库,可以从远程仓库中克隆或者推送。

    但现在我们在学习阶段,自己搭建一个服务器有点小题大做。我们在第10篇《GitHub与远程仓库》中介绍的GitHub就可以作为你使用的远程仓库。

    当你从远程仓库克隆一份到自己的本地时,事实上Git会把你的本地的master分支和远程仓库的master分支对应起来,而且远程仓库的默认名称都是origin。

    查看远程仓库

    $ git remote
    origin
    

    git remote可以用来查看远程仓库的信息,如果你想查看更详细的信息,可以使用:

    $ git remote -v 
    origin  git@github.com:qiaoer2017/test_repository.git (fetch)
    origin  git@github.com:qiaoer2017/test_repository.git (push)
    

    推送分支

    git push命令用于推送分支

    $ git push origin master
    

    推送的时候需要指定推送的是哪一个分支。如果要推送到dev分支:

    $ git push origin dev
    

    当然你也不一定要把所有的分支都推送。一般master和dev需要实时和远程仓库同步,其他分支视情况而定。

    抓取分支

    我们很容易就可以克隆远程仓库到本地。

    $ git clone git@github.com:qiaoer2017/test_respository.git
    

    默认你只能在本地看到master分支。

    $ git branch
    *master
    

    如果你需要在dev上开发,你就必须切换到dev分支,但dev分支又是默认不可见,该怎么办呢?
    很简单:

    $ git checkout -b dev origin/dev
    

    这时你就可以在dev分支上正常工作了。

    处理冲突

    如果你的朋友在dev上向远程提交了一次修改,他在README.md文件最后添加了一行“Good bye!”,并推送到了远程的dev分支上。

    而你也对README.md进行了修改并提交到dev分支,只不过你在文件最后一行添加的和他不一样,你添加的是“Bye bye!”而不是“Good bye!”。这时你再往远程推送的时候会发现失败:

    $ git push origin dev
    To git@github.com:qiaoer/test_repository.git
     ! [rejected]        dev -> dev (fetch first)
    error: failed to push some refs to 'git@github.com:qiaoer2017/test_repository.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.
    

    因为你的修改跟你朋友的修改(你朋友的修改已经推送到了远程库)有冲突,你需要把他的提交从远程库pull下来,在本地合并。

    $ git pull
    remote: Counting objects: 3, done.
    remote: Compressing objects: 100% (2/2), done.
    remote: Total 3 (delta 0), reused 3 (delta 0), pack-reused 0
    Unpacking objects: 100% (3/3), done.
    From github.com:qiaoer/test_repository
       9cc81b9..878a56a  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-to=origin/<branch> dev
    

    git pull也失败了,原因是没有指定本地dev分支与远程origin/dev分支的链接,根据提示,设置dev和origin/dev的链接:

    $ git branch --set-upstream-to=origin/dev dev
    Branch dev set up to track remote branch dev from origin.
    

    然后再:

    $ git pull
    

    然后在本地解决冲突,再提交,再推送。

    相关文章

      网友评论

          本文标题:分支管理(第11篇)

          本文链接:https://www.haomeiwen.com/subject/iigjlttx.html