说明:本文是对Git原理的学习过程中,一些重要的内容摘抄。
里面的图文大部分来自git-sam.com网站。此网站对git的介绍很详细,收益匪浅
https://git-scm.com/book/zh/v1/%E8%B5%B7%E6%AD%A5
Git基本概念
Git其实是一个分布式版本控制系统(Distributed Version Control System,简称 DVCS)。
客户端并不只提取最新版本的文件快照,而是把代码仓库完整地镜像下来。 这么一来,任何一处协同工作用的服务器发生故障,事后都可以用任何一个镜像出来的本地仓库恢复。 因为每一次的克隆操作,实际上都是一次对代码仓库的完整备份。
Git存储方式 -直接记录快照,而非差异比较
Git 在保存和对待各种信息的时候与其它版本控制系统有很大差异。
Git 更像是把数据看作是对小型文件系统的一组快照。 每次你提交更新,或在 Git 中保存项目状态时,它主要对当时的全部文件制作一个快照并保存这个快照的索引。 为了高效,如果文件没有修改,Git 不再重新存储该文件,而是只保留一个链接指向之前存储的文件。 Git 对待数据更像是一个 快照流。
Git分支
Git 保存的不是文件的变化或者差异,而是一系列不同时刻的文件快照。
在进行提交操作时,Git 会保存一个提交对象(commit object)。知道了 Git 保存数据的方式,我们可以很自然的想到——该提交对象会包含一个指向暂存内容快照的指针。 但不仅仅是这样,该提交对象还包含了作者的姓名和邮箱、提交时输入的信息以及指向它的父对象的指针。首次提交产生的提交对象没有父对象,普通提交操作产生的提交对象有一个父对象,而由多个分支合并产生的提交对象有多个父对象,为了更加形象地说明,我们假设现在有一个工作目录,里面包含了三个将要被暂存和提交的文件。
首次提交之后,对应的git仓库的结构如下:
$ git add README test.rb LICENSE
$ git commit -m 'The initial commit of my project'
首次提交对象及其树结构
做些修改后再次提交,那么这次产生的提交对象会包含一个指向上次提交对象(父对象)的指针。
提交对象及其父对象
Git 的分支,其实本质上仅仅是指向提交对象的可变指针。 Git 的默认分支名字是 master。 在多次提交操作之后,你其实已经有一个指向最后那个提交对象的 master 分支。 它会在每次的提交操作中自动向前移动。
分支创建
Git 是怎么创建新分支的呢? 很简单,它只是为你创建了一个可以移动的新的指针。 比如,创建一个 testing 分支, 你需要使用 git branch 命令:
git branch testing
这会在当前所在的提交对象上创建一个指针。
分支切换
要切换到一个已存在的分支,你需要使用 git checkout 命令。 我们现在切换到新创建的 testing 分支去:
git checkout testing
这样 HEAD 就指向 testing 分支了。
HEAD 分支随着提交操作自动向前移动
如图所示,你的 testing 分支向前移动了,但是 master 分支却没有,它仍然指向运行 git checkout 时所指的对象。 这就有意思了,现在我们切换回 master 分支看看:
git checkout master
创建并check分支
想要新建一个分支并同时切换到那个分支上,你可以运行一个带有 -b 参数的 git checkout 命令:
git checkout -b iss53
Switched to a new branch "iss53"
它是下面两条命令的简写:
git branch iss53
git checkout iss53
合并分支
有时候我们会遇到如下的场景,在你拉一个分支修改iss53分支时,中间又需要临时拉一个分支去hotfix修改一个紧急问题
当我们把hotfix修改完,测试没有问题之后,需要把hotfix修改的内容合并到master分支上,进行发布。
git checkout -b hotfix
git checkout master
git merge hotfix
Updating f42c576..3a0874c
Fast-forward
index.html | 2 ++
1 file changed, 2 insertions(+)
在合并的时候,你应该注意到了"快进(fast-forward)"这个词。 由于当前 master 分支所指向的提交是你当前提交(有关 hotfix 的提交)的直接上游,所以 Git 只是简单的将指针向前移动。 换句话说,当你试图合并两个分支时,如果顺着一个分支走下去能够到达另一个分支,那么 Git 在合并两者的时候,只会简单的将指针向前推进(指针右移),因为这种情况下的合并操作没有需要解决的分歧——这就叫做 “快进(fast-forward)”。
合并完成之后,master分支直接往前推进到hotfix分支之后的最新指针状态如下:
合并master最新的代码到当前分支,并继续修改
$ git checkout iss53
Switched to branch 'iss53'
$ git merge master
把最新的分支合并到master
一般在研发过程中,时常也需要把master上别人提交的稳定的最新的代码合并到当前分支。
$ git checkout iss53
Switched to branch 'iss53'
$ git merge master
等当前分支开发完成之后,需要把当前分支合并到master以待发布上线。
假如当前的状态是:
git checkout master
git merge iss53
Auto-merging README
Merge made by the 'recursive' strategy.
README | 1 +
1 file changed, 1 insertion(+)
这次合并操作的底层实现,并不同于之前 hotfix 的并入方式。因为这次你的开发历史是从更早的地方开始分叉的。由于当前 master 分支所指向的提交对象(C4)并不是 iss53 分支的直接祖先,Git 不得不进行一些额外处理。就此例而言,Git 会用两个分支的末端(C4 和 C5)以及它们的共同祖先(C2)进行一次简单的三方合并计算。图 3-16 用红框标出了 Git 用于合并的三个提交对象:
** Git 为分支合并自动识别出最佳的同源合并点。 **
这次,Git 没有简单地把分支指针右移,而是对三方合并后的结果重新做一个新的快照,并自动创建一个指向它的提交对象(C6)(见图 3-17)。这个提交对象比较特殊,它有两个祖先(C4 和 C5)。
值得一提的是 Git 可以自己裁决哪个共同祖先才是最佳合并基础;这和 CVS 或 Subversion(1.5 以后的版本)不同,它们需要开发者手工指定合并基础。所以此特性让 Git 的合并操作比其他系统都要简单不少。
远程分支与本地分支的关系
远程分支
origin master:表示代码服务器上的真正远程仓库的分支。
origin/master:当我们用clone或者fetch命令把服务器上远程分支拉取下来后,在本地git 仓库就有了一个真正远程服务器仓库上分支的一个拷贝,对远程仓库中的分支的索引。我们也叫远程分支。 注意 本地远程仓库(origin/master)是无法在本地进行编辑的,如果要修改只能修改本地master的分支
master:当我们用clone或者fetch命令把服务器上远程分支拉取代码时,除开在远程分支在本地创建一个origin/master的分支索引之后,还会创建一个本地分支,master。
当用Git clone时,Git 克隆会建立你自己的本地分支 master 和远程分支 origin/master,并且将它们都指向 origin 上的 master 分支。
如果clone之后,随着我们在本地研发的推进(commit),会把本地分支的master指针往前推进,而同时随着远程仓库中的master分支有其它研发伙伴push之后在往前推进。
如下图:
通过git fetch获取远程代码的变更
我们在研发过程中可以通过fetch命令把远程仓库中的最新代码变更下载到本地的远程分支,在origin/master与orgin master一致。
推送代码到服务器
可通过git push,把本地分支的代码,推送到远程仓库中,这样其它的同伴就可收通过git fetch获取到你push的最新的代码了。
git push [远程名] [本地分支]:[远程分支]
######### 跟踪远程分支
从远程分支 checkout 出来的本地分支,称为 跟踪分支 (tracking branch)。跟踪分支是一种和某个远程分支有直接联系的本地分支。在跟踪分支里输入 git push,Git 会自行推断应该向哪个服务器的哪个分支推送数据。同样,在这些分支里运行 git pull 会获取所有远程索引,并把它们的数据都合并到本地分支中来。
一个共享共同远程分支的协作研发示例
https://git-scm.com/book/zh/v1/%E5%88%86%E5%B8%83%E5%BC%8F-Git-%E4%B8%BA%E9%A1%B9%E7%9B%AE%E4%BD%9C%E8%B4%A1%E7%8C%AE
分支的https://git-scm.com/book/zh/v1/Git-%E5%88%86%E6%94%AF-%E5%88%86%E6%94%AF%E7%9A%84%E5%8F%98%E5%9F%BA基与rebase
常用命令
分支管理相关
git branch
git branch
不加任何参数,则列出当前所有分支;当前分支前面用"*"标识;
git branch -v
查看各分支最后一个提交对象的信息;
git branch --merged
查看已经被合并的分支;
git branch --no-merged
查看未被合并的分支;
git fetch
把远程仓库中的变更同步获取到本地仓库中。
git push [远程名] [本地分支]:[远程分支]
把本地的分支push到远程仓库中。
相关链接文章:
5.2 分布式 Git - 向一个项目贡献
2.3 Git 基础 - 查看提交历史
2.4 Git 基础 - 撤消操作
2.5 Git 基础 - 远程仓库的使用
3.2 Git 分支 - 分支的新建与合并
3.5 Git 分支 - 远程分支
3.6 Git 分支 - 变基
7.7 Git 工具 - 重置揭密
7.8 Git 工具 - 高级合并
master、origin master 与 origin/master 有什么区别?
master 这个很好理解,它代表本地的某个分支名。
origin master 代表着两个概念,前面的 origin 代表远程名,后面的 master 代表远程分支名。
origin/master 只代表一个概念,即远程分支名,是从远程拉取代码后在本地建立的一份拷贝(因此也有人把它叫作本地分支)。
举几个例子可能会更加清晰地说明问题:
执行 git fetch origin master 时,它的意思是从名为 origin 的远程上拉取名为 master 的分支到本地分支 origin/master 中。既然是拉取代码,当然需要同时指定远程名与分支名,所以分开写。
执行 git merge origin/master 时,它的意思是合并名为 origin/master 的分支到当前所在分支。既然是分支的合并,当然就与远程名没有直接的关系,所以没有出现远程名。需要指定的是被合并的分支。
执行 git push origin master 时,它的意思是推送本地的 master 分支到远程 origin,涉及到远程以及分支,当然也得分开写了