笔者当时学习git的时候对fetch以及pull命令之间的区别疑惑不解,被困扰了许久。其实还是对git的原理理解不深才会有这种情况。git对每次提交都会生成一个cmmit id,我们工作区间版本改变其实就是HEAD指针指向的commit id发生变化。这里引用廖雪峰老师的一幅图来展示一下。
图片来源:git教程
master和dev就是两个分支的指针,而HEAD就是我们当前工作区的指针,它指向的那个就是当前分支,而分支指针指向的就是commit id,上图是一个合并分支过程,将dev分支合并到master分支,其实就是将master指向的commit id变成dev最新的commit id。而当我们的本地版本库与远程版本库建立联系后,本地版本库会创建一个与远程库相关联的文件夹(.git\refs\remotes\)。它的每一个子文件夹就是一个远程库,子文件夹里面保存的就是各个分支文件(其实就是个指针,类似HEAD,里面保存了该分支最新提交的commit id)。
接下来就通过观察这些指针指向的commit id来观察fetch和pull的区别:
将本地库master和远程库master同步后,然后再远程库上做一次修改提交,现在远程库就比本地库多了一个版本。
fetch操作:
local:0243b095ad2940da3c23dd10aa28b5ed6d358852 本地库master现在的版本commitid(.git\refs\heads\master)
remote:def310a721491adb8ad0dc86870c5b027f39602c github上master分支最新的commitid
before:
HEAD-master:0243b095ad2940da3c23dd10aa28b5ed6d358852 master分支最新的commitid(.git\refs\heads\master)
remote-master:0243b095ad2940da3c23dd10aa28b5ed6d358852 本地库关联的远程库最新的commit id(.git\refs\remotes\origin\master)
after:
HEAD-master:0243b095ad2940da3c23dd10aa28b5ed6d358852
remote-master:def310a721491adb8ad0dc86870c5b027f39602c
可以看到,执行fetch后,本地库当前master分支的指针指向并没有变,所以工作区的内容也不会变,但是关联远程库的master分支的指针却变成了最新的commitid。(fetch操作已经把修改内容下载下来)然后再执行merge操作(git merge origin/master),就是将远程库的master分支合并到当前分支即master。
merge操作(gie merge origin/master):
HEAD-master:def310a721491adb8ad0dc86870c5b027f39602c
remote-master:def310a721491adb8ad0dc86870c5b027f39602c
可以看到,当前分支master的指针已经指向最新的commitid,所以工作区的内容也就更新到最新了。
pull 操作:
我们再实验pull操作。远程库依旧比本地库多一次提交
local:4d71a67004b2c78f90404ebd188b8b1ce813a966
remote:090a2c0e8b1e8a41283b4a0596305fb7038265e2
before:
HEAD-local:4d71a67004b2c78f90404ebd188b8b1ce813a966
remote-master:4d71a67004b2c78f90404ebd188b8b1ce813a966
after:
HEAD-master:090a2c0e8b1e8a41283b4a0596305fb7038265e2
remote-master:090a2c0e8b1e8a41283b4a0596305fb7038265e2
我们看到,本地库和关联远程库都更新到了最新一次的提交。
由此可见pull可以当作fetch+merge来用。虽然最后结果是一样的,但是原理不同。
不要用git pull,用git fetch和git merge 代替它。
git pull的问题是它把过程的细节都隐藏了起来,以至于你不用去了解git中各种类型分支的区别和使用方法。当然,多数时候这是没问题的,但一旦代码有问题,你很难找到出错的地方。看起来git pull的用法会使你吃惊,简单看一下git的使用文档应该就能说服你。
将下载(fetch)和合并(merge)放到一个命令里的另外一个弊端是,你的本地工作目录在未经确认的情况下就会被远程分支更新。当然,除非你关闭所有的安全选项,否则git pull在你本地工作目录还不至于造成不可挽回的损失,但很多时候我们宁愿做的慢一些,也不愿意返工重来。
fetch命令:
git fetch 这将更新git remote 中所有的远程repo 所包含分支的最新commit-id, 将其记录到.git/FETCH_HEAD文件中
git fetch remote_repo 这将更新名称为remote_repo 的远程repo上的所有branch的最新commit-id,将其记录。
git fetch remote_repo remote_branch_name 这将这将更新名称为remote_repo 的远程repo上的分支: remote_branch_name
git fetch remote_repo remote_branch_name:local_branch_name 这将这将更新名称为remote_repo 的远程repo上的分支:remote_branch_name ,并在本地创建local_branch_name 本地分支保存远端分支的所有数据。
merge命令:
git merge <branch> # 将branch分支合并到当前分支
git merge origin/master #将远程分支合并到当前分支这两个命令可以加上--no-ff 表示 不要Fast-Foward合并,这样可以生成merge提交,然后需要再加上-m参数,表示对提交的说明
pull命令:
格式:git pull <远程主机名> <远程分支名>:<本地分支名>
git pull origin master:master 将远程库origin的master分支的跟新下载并与master分支合并
git pull origin master 将远程库origin的master分支的更新下载并与当前分支合并(相当于 git fetch origin master + git merge origin/master)
git pull origin 将远程库origin与当前分支存在追踪关系的分支的更新下载并合并到当前分支
git pull 如果当前分支只有一个追踪关系,就将对于远程分支的更新下载并合并到当前分支中