具体请参考:https://git-scm.com/book/zh/v1/Git-%E5%88%86%E6%94%AF-%E4%BD%95%E8%B0%93%E5%88%86%E6%94%AF
Git命令请参考:https://www.yiibai.com/git/git_config.html
1、分支的概念
Git 中的分支,其实本质上仅仅是个指向 commit 对象的可变指针。Git 会使用 master 作为分支的默认名字。在若干次提交后,你其实已经有了一个指向最后一次提交对象的 master 分支,它在每次提交的时候都会自动向前移动。
如下图所示,在你提交了三次后,Git 中的默认分支(主分支)移动到了第三个提交对象上。图中绿色的方框代表一个提交对象,紫色的可以看做是每个提交对象的有关操作(下面内容并不涉及到,可以忽略),灰色的是目前的分支。
2、创建新分支(git branch branchName)
$ git branch branchName //创建的是本地分支,当该分支已经存在时会提示已经存在了
创建新分支时会在当前分支的代码上复制一份放到新分支上。
这会在当前 commit 对象上新建一个分支指针,如下图:
我们可以通过 HEAD 指针来知道当前工作在哪个分支上。在 Git 中,它是一个指向你正在工作中的本地分支的指针(可以将 HEAD 想象为当前分支的别名。)。
运行 git branch
命令,仅仅是建立了一个新的分支,但不会自动切换到这个分支中去。如上例中我们仍然是在 master 分支上工作,如果继续提交也将会只提交在 master 分支上。
3、切换分支(git checkout 分支名)
使用 git checkout 加上分支名来切换分支。不过在切换分支之前,我们必须先把目前所在的分支的修改提交 commit ,否则会报错(因为此时切换分支会导致你在本地未commit的修改将会全部丢失)。
在切换分支前必须先将修改 commit,或者使用 git stash 命令放在堆栈中,等需要的时候再使用 git stash pop 进行应用。切换分支的时最好保持一个清洁的工作区域。
$ git checkout branchName //git checkout origin/master 切换到远程分支上
也可以切换到远程分支上,此时也可以在本地修改然后再推送到远程分支上,此时推送使用:git push origin HEAD:远程分支名
3.1、创建并切换到新分支上(git checkout -b branchName)
要切换到其他分支,可以执行 git checkout
命令。
$ git checkout testing //切换到 testing 分支上 $ git checkout -b testing //新建testing分支并切换到该分支上,相当于同时执行了 git branch testing 和 git checkout testing
这样 HEAD 就指向了 testing 分支
3.2、在不同分支上工作的原理
如果我们继续在 testing 分支上工作时,比如新提交了一次,结果如下图所示:
每次提交后 HEAD 随着分支一起向前移动,现在 testing 分支向前移动了一格,而 master 分支仍然指向原先 git checkout
时所在的 commit 对象。
切换回 master 分支,git checkout master ,结果是如下图的:
这条命令做了两件事。它把 HEAD 指针移回到 master 分支,并把工作目录中的文件换成了 master 分支所指向的快照内容(你的工作目录上的文件将会自动变成在 master 分支上最新的提交版本一样)。也就是说,现在开始所做的改动,将始于本项目中一个较老的版本。它的主要作用是将 testing 分支里作出的修改暂时取消,这样你就可以向另一个方向进行开发。
如果我们在 master 分支上工作,比如在 master 分支上提交了一次之后,我们的项目提交历史产生了分叉,因为刚才我们创建了一个分支,转换到其中进行了一些工作,然后又回到原来的主分支进行了另外一些工作。这些改变分别孤立在不同的分支里:我们可以在不同分支里反复切换,并在时机成熟时把它们合并到一起。如下图:
在Git中创建和销毁分支,在分支上切换等速度非常快,Git 鼓励开发者频繁使用分支
4、合并分支(git merge)
4.1、需要创建新分支的情景
假设我们正在项目的本地 master 分支上工作(一般本地master分支也关联远程master分支),并且已经提交了几次更新
但是项目出现了点问题,为了解决该问题,新建了一个新分支并取名为 iss53,希望在该分支上解决问题过后再合并到master分支上。
$ git checkout -b iss53
接着你开始尝试修复问题,在提交了若干次更新后,iss53
分支的指针也会随着向前推进,因为它就是当前分支
这时项目上出现一个紧急 bug 必须修改,这时我们正在 iss53分支上工作,我们并不需要把目前的工作 push 到远程仓库,唯一需要的仅仅是切换回 master
分支去修改 bug 然后再提交到远程仓库的master分支上。
此时工作目录中的内容和你在解决问题 #53 之前一模一样,你可以集中精力进行紧急修补。这一点值得牢记:Git 会把工作目录的内容恢复为检出某分支时它所指向的那个提交对象的快照。它会自动添加、删除和修改文件以确保目录的内容和你当时提交时完全一样。
接下来我们进行修补bug,我们一般会在 master 分支上新建一个分支来修改 bug ,而不会直接在 master 分支上进行修改。比如创建了一个用来修补 bug 的 hotfix 分支,并在该分支上进行了一系列修补工作:
$ git checkout -b hotfix
4.2、合并分支(git merge 目标分支)
当我们确保在hotfix 上的分支修补是成功的后就可以回到 master
分支将它给合并起来,然后发布到远程仓库上。用 git merge
命令来进行合并(git merge
命令用于合并指定分支到当前所处的分支)
$ git checkout master $ git merge hotfix //合并分支前,先把当前分支的修改提交commit,不然会报错
合并时可能会弹出需要你输入此次合并说明的编辑界面,先按 i 键,输入你的说明,然后 esc ,再输入 :wq 即可。也可以直接esc,然后 :wq 退出。
每次合并都会在本地分支上自动生成一次提交(而被合并的分支并不会生成一个提交对象),你输入的信息或者使用默认信息将会作为本次提交的说明信息。如果合并有冲突的话,Git 不会自动生成一个提交版本,而是显示让你手动解决冲突,然后你需要自己手动进行一次提交。
(请注意,合并时将可能出现“Fast forward”的提示。由于当前 master
分支所在的提交对象是要并入的 hotfix
分支的直接上游,Git 只需把 master
分支指针直接右移。换句话说,如果顺着一个分支走下去可以到达另一个分支的话,那么 Git 在合并两者时,只会简单地把指针右移,因为这种单线的历史分支不存在任何需要解决的分歧,所以这种合并过程可以称为快进(Fast forward)。)
合并之后最新的修改都已经在 master 分支上了,我们就可以将 master 分支push 到远程仓库上。
(注意:合并分支时并不是两个分支内容一样了,只是把目标分支的内容合并到了当前分支,下面的4.2可以看出差别)
4.2、合并两个分叉分支的情况
在上面的情况,master分支和 hotfix 分支并没有分叉,合并时只是 master 指针向右移了而已。(值得注意的是hotfix 分支与 master 分支的合并并不会影响到 iss53 分支,如果需要纳入此次修补,可以用 git merge master
把 master 分支合并到 iss53
;或者等 iss53
完成之后,再将 iss53
分支中的更新并入 master
。)
在删除了 hotfix 分支后,我们现在回到之前未完成任务的 iss53 分支上继续工作。
在完成了在 iss53 分支上的任务后,可以合并回 master
分支。实际操作同前面合并 hotfix
分支一样,只需回到 master
分支,运行 git merge
命令指定要合并进来的分支:
$ git checkout master
$ git merge iss53
不过这次合并操作的底层实现,并不同于之前 hotfix
的并入方式。因为这次你的开发历史是从更早的地方开始分叉的,当前 master
分支所指向的提交对象(C4)并不是 iss53
分支的直接祖先。这次,Git 没有简单地把分支指针右移,而是对三方合并后的结果重新做一个新的快照,并自动创建一个指向它的提交对象(C6)
从上图可以看到,iss53 分支的指针并没有指向 C6,因为是在master分支上进行的合并,只是把 iss53 分支上的内容合并到 master分支上来,此时master 分支上的文件将包含 iss53 分支上的修改,而iss53 分支上并没有因此而发生任何改变。
5、解决冲突
有时候合并操作并不会如此顺利,如果在不同的分支中都修改了同一个文件的同一部分,Git 就无法干净地把两者合到一起此时就会产生冲突(逻辑上说,这种问题只能由人来裁决。),比如下面的master 分支和 FZ02 分支:
Git 将FZ02分支上的内容合并过来了master分支上,但没有提交,它会停下来等你解决冲突。
要看看哪些文件在合并时发生冲突,可以用 git status
任何包含未解决冲突的文件都会以未合并(unmerged)的状态列出。Git 会在有冲突的文件里加入标准的冲突解决标记,可以通过它们来手工定位并解决这些冲突。下面是在 visual studio code 上打开的冲突文件:
<<< HEAD 到 ======= 是自己目前在本地所修改的内容,====== 到 >>>>>> 是你从远程仓库上 pull 拉下来的内容。
你可以把一部分内容删掉,或者两部分内容都保存或者整合起来都由自己决定,还要删除 <<<<<<<
,=======
和 >>>>>>>
等冲突标记。在解决了所有文件里的所有冲突后,运行 git add
将把它们标记为已解决状态,因为一旦暂存,就表示冲突已经解决。
在产生冲突时你可以看到正处于 merging 状态,在手动解决完冲突文件后,首先应该把冲突文件缓存,然后将本次版本提交,提交的这个版本就是作为 master 分支上合并后的提交对象。提交后将自动退出 merging 状态。
5.1、产生冲突时选择退出合并(git reset --hard HEAD)
合并时若有冲突会进入merging状态,git reset --hard HEAD 命令可以退出该状态,相当于退出合并操作,就当做什么也没发生。
6、删除分支(git branch -d 分支名)
在改完 bug 以后,你想要继续回去修改 iss53 的问题。由于当前 hotfix
分支和 master
都指向相同的提交对象,所以 hotfix
已经完成了历史使命,可以删掉了。使用 git branch
的 -d
选项执行删除操作
$ git branch -d hotfix //删除 hotfix 分支 $ git checkout iss53 //回到 iss53 分支继续工作
删除分支时,有可能会报:Cannot delete branch 'xxx' checked out at 'xxxx'。这是因为删除分支时,当前分支不能停留在要删除的分支上,要切换到其他任意分支,才能删除目标分支。
6.1、强制删除未被合并的分支(git branch -D 分支名)
如果某一分支暂未被合并简单地用 git branch -d
删除该分支会提示错误,因为那样做会丢失数据。这时如果我们仍然想删除该分支的话可以使用强制删除命令,比如在 hotfix 分支未被合并到 master 分支前将其删除:
$ git branch -D hotfix
7、查看分支(git branch)
7.1、查看本地分支(git branch)
git branch 命令不仅仅能创建和删除分支,如果不加任何参数,它会给出当前所有分支的清单
注意看 master
分支前的 *
字符:它表示当前所在的分支。也就是说,如果现在提交更新,master
分支将随着开发进度前移。
7.2、查看远程分支(git branch -r)
origin 代表的是默认的远程库,一般是你克隆的时候的库,Git默认起名叫 origin。还有一个库 yc02 是因为我在这个本地仓库上关联了两个远程库。
origin/FZ02 表示远程仓库 origin 上的 FZ02分支
7.3、查看所有分支(git branch -a)
7.3、查看本地分支最新的提交信息(git branch -v)
若要查看各个分支最后一个提交对象的信息,运行 git branch -v
7.4、查看已经与当前分支合并的分支(git branch --merged)
要从该清单中筛选出你已经(或尚未)与当前分支合并的分支,可以用 --merged 。最少都会显示出自己
只会列出刚刚才与当前分支合并的分支名,比如上面的 FZ02 与 master分支合并,这时运行上面代码会显示 FZ02 分支,如果合并后 FZ02 分支再次发生修改并提交了,那么再次在 master 分支上运行该代码不会显示出 FZ02 分支。
一般来说,列表中没有 *
的分支通常都可以用 git branch -d
来删掉。原因很简单,既然已经把它们所包含的工作整合到了其他分支,删掉也不会损失什么。
7.5、查看没有与当前分支合并的分支(git branch --no-merged)
意义与 git branch --merged 相反。
7.6、查看本地分支分别对应关联的远程分支(git branch -vv)
前面的部分是本地分支,后面的是对应关联的远程分支,没有关联的远程分支的话后面就是最近的提交信息。
8、建议的分支管理
仅在 master
分支中保留完全稳定的代码,即已经发布或即将发布的代码。与此同时,建立一个名为 develop
的平行分支专门用于后续的开发,或仅用于稳定性测试,当然并不是说一定要绝对稳定,不过一旦进入某种稳定状态,便可以把它合并到 master
里。
在确保这些已完成的特性分支(比如下面的 topic 分支)能够通过所有测试,并且不会引入更多错误之后,就可以并到develop分支中,然后再可以并到master分支中,等待下一次的发布。
分支管理:
参考:http://www.ruanyifeng.com/blog/2012/07/git.html
其实就是一般只有一个主分支 master,然后有一个开发分支 develop,日常的开发一般都是在 develop 分支上进行。如果进入了某个稳定阶段就可以把dev 分支合并到 master 分支上。常设分支就只有上面两条,但是有一些特殊情况可能要在 dev 分支上开设临时分支。
一种是为了开发某种功能,然后从 dev 分支上分出来,等开发完成再合并到 dev 分支上。
一种是为了修补bug,这个分支可能是因为发布版本出现了bug,所以从 master 分支上分出来一个分支,在该分支上解决 bug 后,再将该分支合并到master分支上,而且也要合并到 dev 分支上。
9、远程分支
从Git远程仓库上克隆下来本地,Git 会自动为你将此远程仓库命名为 origin
,并下载其中所有的数据,建立一个指向远程仓库的 master
分支的指针,在本地命名为 origin/master
。接着,Git 建立一个属于本地 master
分支,跟远程仓库 origin
上 master
分支处于同一版本上。如下图:
一次 Git 克隆会建立你自己的本地分支 master 和远程分支 origin/master,并且将它们都指向 origin
上的 master
分支。
如果其他人向该远程仓库推送了他们的更新,那么远程仓库上的 master
分支就会向前推进。而你在本地 master
分支工作,如果你不和服务器通讯,你的远程分支指针 origin/master
将保持原位不会移动,而本地master指针在不断向前推进。
10、拉取远程分支(git fetch 远程仓库名)
$ git fetch <远程仓库名>
该命令将指定的远程仓库的所有远程分支的更新都同步到本地的远程分支中,由此你在本地的所有远程分支将与远程仓库的分支同步。所取回的更新,在本地主机上要用”远程主机名/分支名”的形式读取。比如origin
主机的master
分支,就可以用origin/master
读取。
但是注意,此时与远程仓库对应的本地分支并没有得到更新,所以要想查看拉取回来的更新必须要切换到本地的远程分支上才能查看:git checkout origin/master。
而要想达到合并,可以使用下面的命令:
$ git fetch origin
$ git merge origin/master
默认情况下,git fetch
取回所有分支的更新。如果只想取回特定分支的更新,可以指定分支名,如下所示
$ git fetch <远程主机名> <分支名> //git fetch origin master 只取回指定分支的更新 $ git fetch //默认拉取 origin 主机的所有分支更新
11、拉取远程分支并且合并(git pull)
git fetch 命令只是将本地的远程分支与远程仓库的分支进行了一个同步更新,本地与远程分支关联的分支并没有得到更新,而要想更新还得手动 git merge。而 git pull 命令相当于同时执行了这两条命令(同时也把该远程仓库的所有远程分支的更新都同步到本地的远程分支中了)。
取回指定的某一远程分支并与本地指定的某一分支合并:
$ git pull <远程主机名> <远程分支名>:<本地分支名> //git pull origin next:master 取回origin主机的next分支,与本地的master分支合并
git pull 相关的命令只会合并对应的本地分支使其更新,其他的本地分支并没有得到更新,但是本地的远程分支都得到了更新(相当于执行了 git fetch origin 然后只合并指定的本地分支)
11.1、取回指定的远程分支并与当前分支合并(git pull origin <远程分支名>)
$ git pull origin branch02 //远程分支 branch02 与当前分支合并 相当于先 git fetch origin 然后再 git merge origin/next
11.2、已建立追踪关系直接拉取(git pull origin、git pull)
如果当前分支已经与远程某一分支之间建立了一种追踪关系(tracking)的话,可以直接省略远程分支名,Git会自动识别出当前分支与远程分支的关联,并且只拉取与之关联的远程分支的更新,其他分支不会得到更新。
(若没有建立追踪关系直接使用该命令将报错)
$ git pull origin
不用远程仓库名时默认指定 origin 仓库(常用命令)
$ git pull //拉取与当前分支关联的远程分支的更新并且合并
12、推送(git push)
要想和其他人分享某个本地分支,你需要把它推送到一个你拥有写权限的远程仓库。你创建的本地分支不会因为你的写入操作而被自动同步到你引入的远程服务器上,你需要主动执行推送分支的操作。你要想把本地某一分支直接推送到远程仓库中(即直接用git push 命令),你必须将本地分支与远程的某一分支关联起来,否则推送不会成功,或者可以手动指定要推送的远程分支。
比如下面我新建了一个 bh03 分支,没有把该分支与远程某一分支关联,推送内容的话就会显示错误:
当然对于无意分享上去的分支,可以只保留为私人的本地分支,而只推送那些协同工作要用到的特性分支。
12.1、推送本地分支到指定的远程分支(git push origin 本地分支名:远程分支名)
使用该命令时不需要提前把本地分支与远程分支关联,比如:
若远程没有上面指定的bh05 分支,将自动创建一个 bh05分支。但注意:使用该命令后指定的本地分支 bh03 与远程分支 bh05 并不会自动关联上了,以后的 bh03 要想推送到 bh05 上都需指定远程分支名,否则还是推送不成功。
12.2、推送到与本地分支同名的远程分支(git push origin 分支名)
若本地分支与远程分支有同名分支,直接使用 git push origin 分支名 命令将自动把指定的本地分支推送到同名的远程分支上,执行该命令不需切换到本地的要推送的分支上,在其他分支推送也可以。
该命令省略的是远程分支名,相当于 git push origin 分支名:远程分支名 。若远程仓库没有与本地分支同名的分支,将在远程仓库创建一个同名的分支。该命令也不会自动关联只是推送,若要关联要加 -u 参数
12.3、已建立追踪关系,直接推送(git push origin、git push)
如果当前分支与远程分支之间存在追踪关系,则本地分支和远程分支都可以省略。(若当前分支与远程分支不存在追踪关系,直接使用该命令将报错)
$ git push origin
不用远程仓库名时默认指定 origin 仓库(常用命令)
$ git push
不带任何参数的 git push
,默认只推送当前分支,这叫做simple
方式。此外,还有一种matching
方式,会推送所有有对应的远程分支的本地分支。在Git 2.0版本之前,默认采用matching
方法,现在改为默认采用simple
方式。如果要修改这个设置,可以采用git config
命令。
$ git config --global push.default matching # 或者 $ git config --global push.default simple
12.4、使用 push 命令删除远程分支(git push origin :master)
如果省略本地分支名,则表示删除指定的远程分支,因为这等同于推送一个空的本地分支到远程分支。
$ git push origin :master //表示删除origin主机的master分支。 # 等同于 $ git push origin --delete master
15、将本地分支与远程分支关联
15.1、本地分支已经存在(git push -u <本地分支名>:<远程分支名>,常用命令)
将某个已存的本地分支关联到远程的分支上,该命令也会执行推送,所以如果指定远程分支上的版本比本地分支的高将会报错
$ git push -u origin <本地分支名>:<远程分支名> $ git push -u origin master //关联到与本地远支同名的远程分支
执行该命令,若远程分支没有指定的分支将会自动新建一个远程分支。注意:git push origin <本地分支名>:<远程分支名>命令只是推送并不会关联。
15.2、本地分支已存在,使用git branch -u 关联
将某个已存的本地分支关联远程分支,先切换到该本地分支上,然后执行: git branch -u origin/远程分支名
或者不用切换到该本地分支上,直接执行: git branch -u origin/远程分支名 本地分支名
15.3、本地分支已存在,通用命令(--set-upstream-to)
直接执行: git branch --set-upstream-to=origin/remote_branch your_branch
若已经切换到要关联的本地分支上,后面的本地分支名可以省略
15.4、本地分支不存在(git checkout -b 新分支名 origin/远程分支名)
将已存的远程分支拉取下来并创建一个本地分支,本地分支与远程分支关联并更新:
$ git checkout -b newBrach origin/master
16、Git 中的 HEAD 和 origin/HEAD
通过查看分支可以看到目前的 HEAD 的指向
可以看到上面有一个 * 符号,还有一个 origin/HEAD
* 号就是 HEAD 的标识,表示 HEAD。HEAD 是一个指针,指向你当前所在的分支。如上图,* 后面的分支就是我目前所在分支。当你切换分支时,HEAD 也会发生改变,会指向切换过去的分支上。
但是如果切换到远程分支上,就会出现 detached HEAD 的情况。如下图,我切换到了远程的 master 分支上:
参考:https://gitbook.tw/chapters/faq/detached-head.html
origin/HEAD :
origin/HEAD也是一个指针,表示默认分支,应该表示的是远程仓库的默认分支吧