在讨论 push 和 pull 的默认行为前我们需要先了解 upstream、downstream 和 FETCH_HEAD 的概念。
upstream && downstream
通俗来说,当本地仓库 R 的某分支 x 的代码 push 到远程仓库 R'的 x'分支时,把 x'分支称为 x 分支的 upstream,相对地把 x 分支称为 x'分支的 downstream。R 和 R'以及 x 和 x'可以不同名,但通常我们都设置为同名(默认同名)。通过 upstream 和 downstream 就建立起了本地分支和远程分支间的映射关系。以下是关于 track 关系的常用命令:
-
查询 upstream 和 downstream 的映射关系
git branch -vv
或cat .git/config
$ git branch -vv => dev 32cf90b [origin/dev] e23rw master 9b04659 [origin/master] dadfa $ cat .git/config => [branch "master"] remote = origin merge = refs/heads/master [branch "dev"] remote = origin merge = refs/heads/dev 复制代码
-
建立当前分支的 upstream
git branch -u [repository-name/branch-name]
$ git branch -u origin/dev => Branch 'dev' set up to track remote branch 'dev' from 'origin'. 复制代码
-
取消其他分支的 upstream
git branch --unset-upstream [branch-name]
省略分支名则表示取消当前分支的 upstream。
//取消dev分支的upstream $ git branch --unset-upstream dev 复制代码
FETCH_HEAD
FETCH_HEAD 是是一个短期引用,表示刚刚从远程获取的分支的最新 commit。每执行一次 fetch 操作都会更新一次 FETCH_HEAD 列表,这个列表存在于.git/FETCH_HEAD
文件中,它的每一行对应一个分支的最新 commit,第一行表示当前分支的最新 commit。
在 master 分支下执行 git fetch,然后查看.git/FETCH_HEAD
文件,输出如下:
9b04659a6105b139fad28018ee0f8c777379134a branch 'master' of https://github.com/fengyueran/test
91edbb325972ffb8e924e664d61aa79054967e12 not-for-merge branch 'dev' of https://github.com/fengyueran/test
32cf90b6fdad208260acde62fa24e72653895758 not-for-merge branch 'dev1' of https://github.com/fengyueran/test
32cf90b6fdad208260acde62fa24e72653895758 not-for-merge branch 'dev2' of https://github.com/fengyueran/test
复制代码
可以看到,直接 git fetch 会获取所有分支的最新 commit,第一行为 master 分支,即当前分支。此时执行 git fetch origin master,然后查看.git/FETCH_HEAD
文件,输出如下:
9b04659a6105b139fad28018ee0f8c777379134a branch 'master' of https://github.com/fengyueran/test
复制代码
可以看到只获取了指定分支 master 的最新 commit。
push
推送到远程分支命令:
$ git push [remote-repository-name] [branch-name]
例: 将代码推送到远程仓库pb的dev分支
$ git push pb dev
复制代码
当我们不显示指定仓库名和分支名而直接用 git push 会是什么效果呢? 对于 origin 仓库的 master 分支,是可以直接推送的,如下:
$ git push
=> Counting objects: 3, done.
Writing objects: 100% (3/3), 201 bytes | 201.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0)
To https://github.com/fengyueran/test.git
* [new branch] master -> master
复制代码
之所以能够直接推送是因为在 clone 的时候会自动创建 master 分支来跟踪 origin/master。
对于非 origin 仓库非 master 分支 git push 会是怎样呢? 如下,切换到 dev 分支然后 git push
$ git checkout -b dev
$ git push
=> fatal: The current branch dev has no upstream branch.
To push the current branch and set the remote as upstream, use
git push --set-upstream origin dev
复制代码
提示没有 upstream 的分支,也就说本地的 dev 分支 push 时不知道推送到远程的哪个分支,也就没法推送。但这取决于具体的 git 配置,在 Git2.0 之前,直接 git push,如果没有 upstream 就以当前分支名作为 upstream。我们可以通过配置 push.default 来改变这种行为,命令如下:
$ git config push.default [option]
复制代码
push.default 选项
push.default 有几个可选值: nothing, current, upstream, simple, matching
- nothing 什么都不推送除非显示地指定远程分支。
- current
推送当前分支到远程同名分支,如果远程不存在,则创建同名分支。
$ git config push.default current $ git push => Total 0 (delta 0), reused 0 (delta 0) remote: remote: Create a pull request for 'dev' on GitHub by visiting: remote: https://github.com/fengyueran/test/pull/new/dev remote: To https://github.com/fengyueran/test.git * [new branch] dev -> dev 复制代码
- upstream 推送当前分支到 upstream 分支上,因此必须有 upstream 分支,这种模式通常用于从一个仓库获取代码的情景。
- simple 和 upstream 类似,不同点在于,必须保证本地分支与 upstream 分支同名,否则不能 push。
- matching 推送所有本地和远程两端都存在的同名分支。
在 Git2.0 之前 push.default 默认值为 matching,2.0 之后默认值为 simple,没有 upstream 不能被推送。
pull
拉取远程分支命令:
$ git pull [remote-repository-name] [branch-name]
例: 拉取远程仓库pb的dev分支
$ git pull pb dev
复制代码
当我们不显示指定仓库名和分支名而直接用 git pull 会是什么效果呢?
事实上 git pull = git fetch + git merge FETCH_HEAD,直接 git pull 时会首先执行 git fetch origin 获取 origin 仓库所有分支,然后执行 git merge FETCH_HEAD 将 FETCH_HEAD(远程某个分支的最新 commit) 合并到当前分支。 如果是 git pull --rebase 第二步则执行 rebase 操作,即 git rebase FETCH_HEAD。
如下: git pull pb dev 相当于分两步执行
- 将 pull 替换为 fetch 执行命令 git fetch pb dev
- git merge FETCH_HEAD
第二步 merge 时会访问 git 的默认配置。
// cat .git/config
[branch "master"]
remote = origin
merge = refs/heads/master
复制代码
可以看到 master 分支的 upstream 为远程的 master 分支,因此在 pull 合并时会合并远程的 master 分支,这里的 refs/heads/master 指的是远程仓库的 master 分支。当然我们可以修改这个配置,重新建立 upstream 或直接修改配置。 这里直接通过以下命令修改配置
git config branch.master.merge refs/heads/dev
复制代码
此时,再查看配置
[branch "master"]
remote = origin
merge = refs/heads/dev
复制代码
merge 的值变成了 refs/heads/dev,也就是说在 master 分支运行 pull 再 merge 的时候会 merge 远程仓库的 dev 分支而不是 master 分支。