Git学习8:Git分支操作

来源:互联网 发布:盐城大数据产业园 编辑:程序博客网 时间:2024/05/21 22:49

认识分支

Git中分支管理是Git的一大特色,由于在实际项目开发中的也确定性以及需求变更的复杂性,所有的开发都集中在一条分支上开发势必造成开发与维护成本的提高。以软件项目开发为例,由于开发会有多个开发周期,发布多个软件版本,每个已发布的版本又可能出现Bug,以及适应时代的变化,不断升级软件,推出新功能。这些都是开发中可能出现的情况,试想这些开发都在一个分支(目前是master)上进行开发,那么会严重影响软件的开发进度,这样算下来的时间成本是非常之高的。现在流行的敏捷开发方式就需要完善的分支管理方案。

使用分支将某个功能或者Bug的修复独立出来,这样就实现了与主开发分支的隔离,不同开发组的开发工作也不会发生冲突,大大提高了开发效率。

分支操作

分支命令

在Git中使用非分支操作命令是git branch。该命令的用法有如下几种:

//查看分支git branch//创建分支git branch <branchname>//从某个起始点创建分支git branch <branchname> <start-point>//删除分支git branch -d <branchname>//强制删除分支git branch -D <branchname>//重命名分支git branch -m <oldbranch> <newbranch>//强制重命名分支git branch -M <oldbranch> <newbranch>

分支操作实例

继续上一篇文章的操作,查看之前的提交日志:

项目初始化

//配置命令别名git config --global alias.lol "log --oneline --decorate --graph --all"//之后就可以使用git lol查看日志了git lol

输出如下:

*   e4e0025 (HEAD -> master, origin/master) Merge tag 'mytag3' of file:///home/rhwayfun/java/notes/repos2/share|\  | * 1ed8141 (tag: mytag3) blank commit for GnuPG-signed tag test.|/  * 6d095e6 blank commit for annotated tag test.* abb48d5 (tag: mytag) blank commit.*   1e5c656 Merge README.txt: Hello, user2 and user1.|\  | * 04eed97 create README.txt* | b14d8be create README.txt|/  *   0432ca1 Merge branch 'master' of file:///home/rhwayfun/java/notes/repos2/share|\  | * 5f642a9 create team/user2.txt* 14cc834 create team/user1.txt

现在开始在这个基础上执行分支操作,为了与之前的提交区别开,开始先创建一个空提交,然后在该提交创建带说明的里程碑(上一篇有讲解哦),执行操作如下:

//创建一个空白提交git commit --allow-empty -m "start git branch learning."//创建里程碑v1.0git tag -m "Release 1.0" v1.0//将里程碑推送到共享仓库git push origin v1.0

user1完成speak功能

现在已经创建了里程碑并推送到了远程分支,接下来就开始创建分支,并在分支上进行开发了。

首先需要创建分支:

//切换到user1的工程目录cd user1/project//创建user1/dev分支git branch user1/dev//切换到user1/dev分支git checkout user1/dev

查看创建的分支:

master
* user1/dev

这样就把分支切换到user1/dev分支上了,在user1/dev分支需要实现人说话和走路的功能,说话的功能由user1实现,走路的功能由user2实现。执行如下操作:

//创建PersonService接口touch src/main/java/PersonService.java//编辑PersonService.javavim src/main/java/PersonService.java

在接口中定义了两个方法:说话和走路的方法。

package com.rhwayfun.hello.service;public interface PersonService{        //speak--->assign to user1 to finish.        void speak();        //walk--->assign to user2 to finish.        void walk();}

编译PersonService.java文件:

javac -d /home/rhwayfun/java/notes/to2/user1/project/src/main/java/com/rhwayfun/hello/service src/main/java/com/rhwayfun/hello/service/PersonService.java

创建PersonServiceImpl.java文件,然后由user1实现speak方法,如下:

package com.rhwayfun.hello.service.impl;import com.rhwayfun.hello.service.PersonService;public class PersonServiceImpl implements PersonService {        //finish by user1.        public void speak(){                System.out.println("I can speak English.");        }        //finish by user2.        public void walk(){                // to do by user2        }}

现在user1已经完成了speak功能的开发,然后执行如下操作将修改推送到共享版本库:

//将工作区所有的修改都添加到暂存区git add .//提交到本地版本库git commit -m "user1 finish speak function."//切换到master主分支git checkout master//合并user1/dev开发分支git merge user1/dev

这样在本地版本库就有了最新的提交,在正式推送到共享仓库之前,可以简单分析以上命令的执行过程,分支实际上是在目录.git/refs/heads下的引用,版本库初始时创建的master分支就在该目录下,为了验证这一点,可以执行如下命令:

ls -F .git/refs/heads

输出结果为:

master
user1/

ls -F .git/refs/user1

输出结果为:

dev

创建非分支user1/dev是基于头指针HEAD创建的,因此当前分支和master分支的指向是一致的。

现在可以将user1的成果推送到共享版本库了,只需要执行git push就可以完成。接下来的走路的功能需要user2来完成,因此首先切换到user2的工作区:

cd user2/project//拉取最新的提交git pull

user2完成walk功能

现在user2已经拥有了最新的功能,为了不影响master主分支的开发,需要将walk功能的开发切换到一个分支上进行:

//创建user2/dev分支并切换到该分支上git checkout -b user2/dev

在PersonServiceImpl.java文件中完成walk方法的开发,代码如下:

...    //finish by user2.    public void walk(){        // to do by user2        System.out.println("Nice, I can walk.");    }...

这样user2就完成了walk功能的开发,执行如下操作将user2完成的功能推送到共享版本库:

git add .git commit -m "user2 finish walk function."git checkout mastergit merge user2/devgit tag -m "Release 1.1" v1.1git push//推送里程碑v1.1到远程仓库git push origin v1.1

要注意的是里程碑必须额外手动推送到远程仓库,不然直接执行git push只会将master分支的修改推送到远程仓库。最后我们可以看看user1和user2完成PersonService接口开发的提交日志情况:

* 4fc2175 (HEAD -> master, tag: v1.1, origin/master) user2 finish walk function.* f62c2cf user1 finish speak function.* 8f126fc (tag: v1.0) start git branch leanring.*   e4e0025 Merge tag 'mytag3' of file:///home/rhwayfun/java/notes/repos2/share|\  | * 1ed8141 (tag: mytag3) blank commit for GnuPG-signed tag test.|/  * 6d095e6 blank commit for annotated tag test.* abb48d5 (tag: mytag2, tag: mytag) blank commit.*   1e5c656 Merge README.txt: Hello, user2 and user1.|\  | * 04eed97 create README.txt* | b14d8be create README.txt|/  *   0432ca1 Merge branch 'master' of file:///home/rhwayfun/java/notes/repos2/share|\  | * 5f642a9 create team/user2.txt* 14cc834 create team/user1.txt

Bug分支协同操作

现在user1和user2已经通过协作共同完成PersonService接口的开发,嗯,非常好,这个过程通过使用Git 分支操作很顺利地完成了功能的开发。现在因为需求变更,需要修改speak和walk功能的实现,因为两个功能是由两个开发者user1和user2协同完成的,所以需要创建一个bug分支,修复代码并提交。

在user1的工作区下执行如下命令:

//创建协同工作分支git checkout -b hello-1.x v1.1//查看两者的对应的提交git rev-parse hello-1.x v1.1^{} master

修改PersonServiceImpl.java代码如下:

...    //finish by user1.    public void speak(){        System.out.println("I can speak English.");     }...

这样user1就完成了speak功能的bug修复,于是执行如下操作:

git add -ugit commit -m "fix speak bug."git checkout mastergit merge hello-1.x git push origin hello-1.x 

现在已经把需要协同处理的bug分支提交到共享仓库了,切换到user2的工作区目录下,执行如下命令:

//获取共享最新的更新,但不执行merge操作git fetch//显示远程仓库指向的引用git ls-remote origin//从上一个命令中可以发现有一个hello-1.x的bug分支//从远程仓库origin/hello-1.x分支创建本地hello-1.x分支并切换到该分支git checkout -b hello-1.x origin/hello-1.x

打开PersonServiceImpl.java文件,修改walk方法的代码如下:

...    //finish by user2.    public void walk(){        // to do by user2        System.out.println("Oh oh, I can only walk.");    }...

这样user2也完成了bug的修复,于是执行如下操作:

git add -ugit commit -m "fix walk bug."git checkout mastergit merge hello-1.xgit push

现在user2已经最后的bug修复修复结果合并到master分支了,为了保持同步,user1需要执行如下操作:

git pull//因为user1的本地hello-1.x分支不是最新的git checkout hello-1.x//合并到master分支git merge master

完成以上bug修复之后,显示的最新提交日志输出如下:

* 0b8844a (HEAD -> master, origin/master, hello-1.x) fix walk bug.* 55889c6 (origin/hello-1.x) fix speak bug.* 4fc2175 (tag: v1.1) user2 finish walk function.* f62c2cf user1 finish speak function.* 8f126fc (tag: v1.0) start git branch leanring.*   e4e0025 Merge tag 'mytag3' of file:///home/rhwayfun/java/notes/repos2/share|\  | * 1ed8141 (tag: mytag3) blank commit for GnuPG-signed tag test.|/  * 6d095e6 blank commit for annotated tag test.* abb48d5 (tag: mytag2, tag: mytag) blank commit.*   1e5c656 Merge README.txt: Hello, user2 and user1.|\  | * 04eed97 create README.txt* | b14d8be create README.txt|/  *   0432ca1 Merge branch 'master' of file:///home/rhwayfun/java/notes/repos2/share|\  | * 5f642a9 create team/user2.txt* 14cc834 create team/user1.txt

变基操作和拣选操作

变基操作使用的命令是git rebase
拣选操作使用的命令是git cherry-pick

先说说拣选操作,可以选择某一个分支中的一个或几个commit(s)来进行操作,使用的场景有两个:

1、把弄错分支的提交移动到正确的地方
2、把其他分支的提交添加到现在的分支

再说说变基操作,可以改写、替换、删除或合并提交。常在需要合并的场景下使用,而合并和变基的操作可以使用如下简图加以区别:

假设初始状态是这样的:

初始状态

合并操作是这样的:

git-merge

变基操作是这样的:

git-rebase

可以看到变基操作不会产生新的提交,降低了代码维护的成本,而且合并方式更加优雅。

下面通过实际操作演示变基操作:

在user2的工作区执行如下操作:

touch rebase.txtvim rebase.txt

编辑rebase.txt文件如下:

This is rebase operatio by user2.

之后将所做的修改提交到本地仓库,然后推送到共享版本库,这个过程执行的一系列操作如下:

git add rebase.txtgit commit -m "create rebase.txt file"git checkout mastertouch rebase.txtvim rebase.txt

在user2的master分支编辑rebase.txt文件如下:

It is a rebase operation by user2.

之后执行如下操作,在master分支上添加到本地仓库,这样做的目的是造成变基操作过程冲突:

git add .git commit -m "create rebase.txt on master."git checkout user2/devgit rebase master

在执行变基操作git rebase时出现如下错误:

First, rewinding head to replay your work on top of it…
Applying: create rebase.txt file
Using index info to reconstruct a base tree…
Falling back to patching base and 3-way merge…
Auto-merging rebase.txt
CONFLICT (add/add): Merge conflict in rebase.txt
Failed to merge in the changes.
Patch failed at 0001 create rebase.txt file
The copy of the patch that failed is found in:
/home/rhwayfun/java/notes/to2/user2/project/.git/rebase-apply/patch
When you have resolved this problem, run “git rebase –continue”.
If you prefer to skip this patch, run “git rebase –skip” instead.
To check out the original branch and stop rebasing, run “git rebase –abort”.

意思就是变基操作出现了冲突,需要在工作区处理冲突后将处理冲突的文件添加到暂存区,然后继续变基操作。同时我们直到发生冲突的文件是rebase.txt文件,将工作区的rebase.txt文件修改如下:

This is a rebase operation by user2.

再执行如下操作,完成最终的变基操作:

git add -ugit rebase --continuegit checkout mastergit merge user2/dev //用本地的user2/dev分支直接更新远程版本库的master分支git push origin user2/dev:master

这样就是完成了变基操作,而拣选操作就很好理解了,可以在不同的分支上将其他的commit拿过来并接到该分支之后,就完成了拣选操作。

小结

通过以上实例的演示,可以非常清楚地看到分支操作在协同开发中的重要性,分支操作大大节省了开发时间,所以好好掌握分支操作是很重要的。此外,演示中还将Git中比较难以理解的变基操作、合并操作以及拣选操作进行了说明和演示。变基操作相比合并操作是一种更优雅的合并方式,因为合并操作会产生第三个提交,在合并操作很多的时候,代码维护的难度就会上升。

2 0