Git中的push和pull的預設行為
在討論 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 =>dev32cf90b [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
檔案,輸出如下:
9b04659a6105b139fad28018ee0f8c777379134abranch 'master' of https://github.com/fengyueran/test 91edbb325972ffb8e924e664d61aa79054967e12not-for-mergebranch 'dev' of https://github.com/fengyueran/test 32cf90b6fdad208260acde62fa24e72653895758not-for-mergebranch 'dev1' of https://github.com/fengyueran/test 32cf90b6fdad208260acde62fa24e72653895758not-for-mergebranch 'dev2' of https://github.com/fengyueran/test 複製程式碼
可以看到,直接 git fetch 會獲取所有分支的最新 commit,第一行為 master 分支,即當前分支。此時執行 git fetch origin master,然後檢視.git/FETCH_HEAD
檔案,輸出如下:
9b04659a6105b139fad28018ee0f8c777379134abranch '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 分支。