git分支

作者: 学习编程王同学 | 来源:发表于2018-01-16 15:56 被阅读31次

    什么是git分支?

    什么是git分支?首先让我们回顾一下提交对象,一个提交对象(commit objects)包括:

    • 一系列文件在某个时间的快照。
    • 一系列指向父提交对象的索引。
    • 一个SHA-1名字,这个名字40个字符长,是独一无二的。
    • 作者的姓名和邮箱,以及提交时对提交的描述。

    事实上,“一系列文件在某个时间的快照”并不是直接存在于提交对象。在git中,blob对象保存着文件的快照,树对象保存着目录结构和blob对象的索引,而提交对象保存指向树对象的指针。下图是一个这三者关系的示意图:

    三个对象及其关系

    那么git中的分支是什么呢?

    git中的分支就像是你的文件的一份副本,你可以在需要的时候拷贝一份出来,这样你就得到了一个“分支”,你可以在上面修改,修改完成之后再合并回去。在一些版本控制软件中实际情况确实是这样,然而在git中并非如此

    在git中,对分支的操作大部分只是在修改指向提交对象的heads。我们知道,heads是一个指向提交对象的指针,分支操作中的大部分操作只需要修改heads的指向,即向heads文件中写入41个字符即可(40个SHA-1字符串和1个换行符)。与其他一些版本控制软件采用的复制文件策略相比较,git分支操作与文件大小无关,操作迅速快捷。

    指向提交对象的heads

    创建分支

    现在先来看看我们在哪个分支,使用git branch命令查看当前分支,命令选项-v显示分支指向提交对象的校验和及其描述:

    $ git branch
    * master
    $ git branch -v
    * master 57b75e6 Add GitHub description.
    

    从结果中看到,现在只有一个分支,叫做master*表示当前所在的分支,即HEAD的指向。

    用图简略表示如下:

    仅有master分支

    现在创建一条dev分支,使用git branch <branchname>命令:

    $ git branch dev
    $ git branch
      dev
    * master
    

    现在有了两条分支:masterdev,目前我们在master分支。图示如下:

    创建一条dev分支

    可见,事实上只是创建了一个指向图中提交对象C3的指针,使用git log --decorate可以查看heads的指向:

    $ git log --oneline --decorate -3
    57b75e6 (HEAD -> master, origin/master, dev) Add GitHub description.
    beac1f4 make README.md more friendly.
    14bd627 add two wrong line to README.md
    

    master、远程originmasterdev指向57b75e6提交对象,HEAD指向master

    切换分支

    现在切换到dev分支,使用git checkout <branchname>命令,在切换前请确保你的工作目录是干净的:

    $ git checkout dev
    Switched to branch 'dev'
    

    这样就切换到了dev分支,查看一下:

    $ git branch
    * dev
      master
    $ git log --oneline --decorate -3
    57b75e6 (HEAD -> dev, origin/master, master) Add GitHub description.
    beac1f4 make README.md more friendly.
    14bd627 add two wrong line to README.md
    

    可以看到我们确实在dev分支,HEAD确实指向了dev分支。在切换分支时,git会将分支所指向的提交对象的文件快照检出到工作目录,并且更改HEAD的指向。目前分支情况图示如下:

    切换到dev分支

    git checkout -b <branchname>可以创建<branchname>分支并且切换到它,相当于执行下面两条命令:

    $ git branch <branchname>
    $ git checkout <branchname>
    

    “快进”合并

    现在在dev分支,我们创建一个dev.md文件并且提交:

    $ touch dev.md
    $ git add dev.md
    $ git commit -m "add dev.md"
    [dev fd2e1cb] add dev.md
     1 file changed, 0 insertions(+), 0 deletions(-)
     create mode 100644 dev.md
    

    查看一下各个分支所指:

    $ git log --oneline --decorate -3
    fd2e1cb (HEAD -> dev) add dev.md
    57b75e6 (origin/master, master) Add GitHub description.
    beac1f4 make README.md more friendly.
    

    dev前进了一个提交对象,HEAD指向dev,其他分支并没有更改,图式如下:

    dev新提交

    现在切换到master,使用$ git checkout master命令,HEAD会指向master,工作目录中的文件将会被替换:

    切换回master

    合并分支使用git merge <branchname>命令,这个命令将<branchname>分支合并到当前分支,现在我们在master分支,执行下面的命令将dev分支合并到master分支:

    $ git merge dev
    Updating 57b75e6..fd2e1cb
    Fast-forward
     dev.md | 0
     1 file changed, 0 insertions(+), 0 deletions(-)
     create mode 100644 dev.md
    

    git告诉我们此次合并的方式是Fast-forward(快进),此时分支情况如下:

    合并dev分支

    现在dev分支已经被合并到master分支了。从上图可以看出git仅仅是简单的更新了masterHEAD的指向,这是由于合并前master指向dev的直接上游,这种合并方式叫做快进(Fast-forward)。

    可以使用--no-ff选项避免使用“快进”合并,这样会形成一个新的合并提交,类似下节讲到的分之合并:

    $ git merge --no-ff dev
    

    现在dev分支已经充分得发挥了自己的作用,让我们删除它:

    $ git branch -d dev
    Deleted branch dev (was fd2e1cb).
    

    如果一个分支没有完全合并到当前分支,那么git会阻止你删除它,如果确实要删除它,使用-D命令选项:

    $ git branch -D <branchname>
    

    如果想要知道那些分支被合并了或者没有合并,使用下面的命令:

    $ git branch --merged       # 查看已经被合并的分支
    $ git branch --no-merged    # 查看还没有被合并的分支
    

    目前分支情况如下:

    删除dev分支

    本文所讲的例子整体过程图示如下:

    快进合并

    分支合并

    现在创建一个testing分支并且切换到该分支:

    $ git checkout -b testing
    Switched to a new branch 'testing'
    

    添加testing.md并提交,修改tesing.md并提交:

    $ touch testing.md
    $ git add testing.md
    $ git commit -m "add testing.md"
    [testing dd4555e] add testing.md
     1 file changed, 0 insertions(+), 0 deletions(-)
     create mode 100644 testing.md
    $ echo "A file added in testing branch." > testing.md 
    $ git commit -a -m "add description of testing.md"
    [testing 40a00ae] add description of testing.md
     1 file changed, 1 insertion(+)
    

    回到master分支并且修改dev.md

    $ git checkout master 
    Switched to branch 'master'
    $ echo "A dev file." > dev.md
    $ ls
    dev.md  README.md
    $ git commit -a -m "add description of dev.md"
    [master 1b63c87] add description of dev.md
     1 file changed, 1 insertion(+)
    

    现在两条分支在分叉后都有新的提交:testing有两个新的提交,master有一个新的提交。怎样在命令行查看呢?

    $ git log --oneline --decorate --graph --all
    * 1b63c87 (HEAD -> master) add description of dev.md
    | * 40a00ae (testing) add description of testing.md
    | * dd4555e add testing.md
    |/  
    * fd2e1cb add dev.md
    * 57b75e6 (origin/master) Add GitHub description.
    
    # 省略
    

    可以看到,在fd2e1cb分支分叉,testing之后进行了两次提交,master进行了一次提交,目前我们在master分支。图示如下:

    合并提交1

    现在将testing分支合并到master分支:

    $ git merge testing
    Merge made by the 'recursive' strategy.
     testing.md | 1 +
     1 file changed, 1 insertion(+)
     create mode 100644 testing.md
    $ git log --oneline --decorate --graph --all
    *   8425ef2 (HEAD -> master) Merge branch 'testing'
    |\  
    | * 40a00ae (testing) add description of testing.md
    | * dd4555e add testing.md
    * | 1b63c87 add description of dev.md
    |/  
    * fd2e1cb add dev.md
    * 57b75e6 (origin/master) Add GitHub description.
    
    # 省略
    

    现在git帮我们合并了mastertesting,并且生成了一个新的提交(你可能需要填写提交描述),这个新提交的SHA-1校验和前七位是8425ef2。

    git能够帮我们自动合并,而不会产生冲突的原因是我们在不同的分支中修改了不同的文件,此时git会参考两个分支所指的快照(testing40a00aemaster1b63c87)和两个分支的共同祖先(fd2e1cb),自动合并。参考的三个快照分别相当于下图的C6、C7和C4.

    新生成的提交叫做合并提交,相当于下图的C8.这个新提交拥有两个父提交。

    合并提交2

    好了,现在删掉testing分支吧:

    $ git branch -d testing 
    Deleted branch testing (was 40a00ae).
    

    本文所讲的分支合并的整体过程图示如下:

    分之合并

    冲突解决

    如果在不同分支中同一个文件的同一个地方做了修改,git就无法干净利落地合并它们。

    创建一个新的分支iss1,在iss1分支中将README.md修改如下并且提交:

    $ git checkout -b iss1
    Switched to a new branch 'iss1'
    $ vim README.md 
    $ cat README.md 
    # Hi, Git!
    
    This is my first git project and i use it to learn git.
    
    Git is a free and open source distributed version control system.
    $ git commit -a -m "change README.md in iss1"
    [iss1 d6801d6] change README.md in iss1
     1 file changed, 6 deletions(-)
    

    切换到master分支,将README.md修改如下并且提交:

    $ git checkout master
    Switched to branch 'master'
    $ vim README.md 
    $ cat README.md 
    # Hi, Git!
    
    This is my first git project and i use it to learn git.
    
    I LOVE GIT.
    $ git commit -a -m "change README.md in master."
    [master 63172f9] change README.md in master.
     1 file changed, 1 insertion(+), 7 deletions(-)
     $ git log --oneline --decorate --graph --all
    * 63172f9 (HEAD -> master) change README.md in master.
    | * d6801d6 (iss1) change README.md in iss1
    |/  
    *   8425ef2 Merge branch 'testing'
    
    # 省略
    

    现在将iss1分支合并到master分支:

    $ git merge iss1
    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在遇到冲突时,并不会创建一个合并提交,而是暂停下来,等用户解决冲突之后,由用户提交。

    含有冲突的文件被标记为“未合并”(unmerged)状态,随时可以使用git status来查看:

    $ git status
    On branch master
    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中的冲突,首先来看一看git刚刚所做的工作:

    $ cat README.md 
    # Hi, Git!
    
    This is my first git project and i use it to learn git.
    
    <<<<<<< HEAD
    I LOVE GIT.
    =======
    Git is a free and open source distributed version control system.
    >>>>>>> iss1
    

    其中的一部分是git为我们标记的冲突的部分:

    <<<<<<< HEAD
    I LOVE GIT.
    =======
    Git is a free and open source distributed version control system.
    >>>>>>> iss1
    

    =======的上半部分的是HEAD分支中的文件内容,在其下半部分的是iss1分支中文件的内容。

    现在让我们将这部分修改如下:

    I LOVE GIT.
    

    这表示将丢弃iss1中的修改,当然你可以根据自己的喜好更改,你可以改成任意你需要的内容。

    现在将文件添加到暂存区,并且查看状态:

    $ git add README.md 
    $ git status
    On branch master
    All conflicts fixed but you are still merging.
      (use "git commit" to conclude merge)
    
    nothing to commit, working directory clean
    

    可见,一旦冲突文件被添加到暂存区,它的“未合并”状态就会被解除,即表示冲突已经解决。

    现在提交即可:

    $ git commit -m "merge iss1"
    [master 11f0f7a] merge iss1
    $ git log --oneline --decorate --graph --all
    *   11f0f7a (HEAD -> master) merge iss1
    |\  
    | * d6801d6 (iss1) change README.md in iss1
    * | 63172f9 change README.md in master.
    |/  
    *   8425ef2 Merge branch 'testing'
    
    # 省略
    

    最后删除iss1分支:

    $ git branch -d iss1
    Deleted branch iss1 (was d6801d6).
    

    储藏与清理

    git在切换分支时必须保证当前工作目录是干净的,如果现在做了一点更改,不至于提交一次新的更新,但是却必须更换到另一条分支上,怎么办呢?

    git为我们提供了stash(储藏)工具。

    现在在master分支上对README.md作一些更改,并且将它储藏起来:

    $ git status -s
     M README.md
    $ git stash 
    Saved working directory and index state WIP on master: 11f0f7a merge iss1
    HEAD is now at 11f0f7a merge iss1
    $ git status -s
    $ 
    

    在运行git stash之后工作目录就变干净了,现在就可以切换到其他分支工作啦。

    在其他分支工作完之后,又回到master,怎样继续工作呢?

    使用git stash list命令可以查看储藏的列表:

    $ git stash list
    stash@{0}: WIP on master: 11f0f7a merge iss1
    

    使用git stash apply <stashname>即可应用,如果<stashname>为空,则会应用最新的储藏:

    $ git stash apply
    $ git stash list
    stash@{0}: WIP on master: 11f0f7a merge iss1
    $ git stash apply
    On branch master
    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.md
    
    no changes added to commit (use "git add" and/or "git commit -a")
    $ git status -s
     M README.md
    

    我们的更改又回来了,使用git stash drop <stashname>删除相应的储藏,如果<stashname>为空,则会删除最新的储藏:

    $ git stash drop
    Dropped refs/stash@{0} (939ab1d7c4f88fe2dd9b3420d0cf919a668eff23)
    $ git stash list
    $ 
    

    可以使用git stash pop直接应用最新的储藏,同时删除该储藏。

    在git中,可以进行多次储藏,也可以在不同的分支应用储藏。

    相关文章

      网友评论

          本文标题:git分支

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