![](/img/trans.png)
[英]How to rebase to master instead of git pull origin master?
[英]Why is "master" required in git pull --rebase origin master but not git rebase -i origin?
當我想針對遠程主機重新設置基准時,我使用
git pull --rebase origin master
如果我使用
git pull --rebase origin
我收到錯誤
You asked to pull from the remote 'origin', but did not specify
a branch. Because this is not the default configured remote
for your current branch, you must specify a branch on the command line.
但是為什么會這樣
git rebase -i origin
作品?
在這種情況下
git rebase -i origin master
實際上導致
fatal: fatal: no such branch/commit 'master'
我沒有名為 master 的本地分支,但是為什么在這種情況下它不去遠程分支呢?
git pull
命令與大多數其他 Git 命令完全不同。 I'd say that in many ways, the closest other Git command is git gc
, which—like git pull
—is a convenience wrapper to avoid needing to type in multiple separate Git commands. 1
git pull
的作用是:
git fetch
; 然后 第一個命令git fetch
需要遠程的名稱。 名稱origin
是第一個遠程的標准名稱,並且由於大多數 Git 存儲庫只有一個遠程,所以origin
是該存儲庫中第一個、最后一個和唯一遠程的名稱。
你可以把它關掉——你可以運行git pull
而不需要額外的參數——Git 會找到一些合適的遙控器。 但是如果你要提供額外的 arguments,第一個非選項參數是遠程名稱,所以git pull frabjous
使用frabjous
作為遠程名稱。
第二個命令是git merge
或git rebase
。 2第二個命令需要一個提交 hash ID ,例如4c53a8c20f8984adb226293a3ffd7b88c3f4ac1a
,或者可以代替提交 Z0800FC577294C34E0B28AD283943594Z ID 的東西。 3但是,通常情況下,我們在這里使用一個名稱——一個分支名稱,如master
或main
或dev
或其他名稱——作為“可行的東西”。 總的想法git pull
的思考方式——是:從其他人那里獲取東西,然后合並它。 這里的“其他人”是遙控器,“要獲取的東西”是“他在某個分支上的任何新提交”。 所以你在這里輸入的名字,當你輸入一個名字時,就是另一個人的分支名稱。
請注意,與git fetch
一樣,您可以忽略所有這些,然后運行:
git pull
pull 命令將根據您為當前分支設置的上游計算出要使用的遠程- 可能是origin
- 和要使用的名稱,這一切都是獨立的。 “上游”只是你可以設置的東西:對於名為xyzzy
的分支,上游可能已經設置為origin/xyzzy
。
請注意,此處的上游名稱origin/xyzzy
中有一個斜杠:它由遠程名稱origin
,然后是斜杠,然后是遠程分支名稱xyzzy
。 因此,如果在遙控器上看到的分支名稱是frab/jous
,那么您將在此處使用origin/frab/jous
,並帶有兩個斜杠:一個用於將origin
與另一個人的分支名稱分開,另一個在另一個人的分支名稱中。
如果你要輸入一個名字,在你的git pull
命令上,你必須把它放在 remote 之后。 完成此操作后,Git 假設您只需輸入遠程上看到的分支名稱。 所以你輸入:
git pull origin frab/jous
或者這里的任何東西,意思是:
git fetch origin
; 然后origin/frab/jous
為 hash ID 並酌情運行git merge
或git rebase
。 請注意,這兩個步驟中的任何一個都可能完全失敗,而第二個步驟可能會在中間停止。 如果一個步驟失敗,則任何剩余步驟都不會發生,並且您應該從失敗點重新開始,無論是什么,如果您想從中斷的地方繼續 - 所以您需要知道哪一步失敗,如果有一個失敗。 幸運的是,對於我們大多數人來說, git fetch
運行額外的時間是非常安全的,所以我們幾乎可以忽略它的失敗與成功。 但是您仍然需要知道是完成中間停止的合並還是變基。 由於這個和其他原因,我總是鼓勵 Git 新手先學習單獨的命令。 認識到他們何時工作、何時完全失敗以及何時停止是很重要的。
不幸的是,這意味着你需要了解這個奇怪之處,其中git pull
取另一個人的名字作為分支(省略了origin/
),而git merge
或git rebase
取了你的名字(包括origin/
Z rebase)。 但是無論如何你都必須學習這個。 記下它! 他們的分支名稱是他們的; 您的Git 存儲庫從中讀取它們的名稱和哈希 ID 值(在git fetch
步驟期間),並將它們存儲在您的Git 存儲庫中這些origin/
下。
這還是漏掉了很多。 Git 的設置學習曲線非常陡峭。 我現在休息一下腳注,然后討論另一件事。
1 git gc
runs git repack
, git prune-packed
, git reflog expire
, git worktree prune
, git prune
, git pack-refs
, and/or git rerere gc
if/as appropriate. 這並不意味着是一個完全詳盡的列表,因為列表有時會發生變化(例如, git worktree
在 Git 2.5 之前不存在)而且我並沒有真正跟蹤。 我通過瀏覽git gc
文檔生成了這個列表。 我認為這個特定的手冊頁可能是https://git-man-page-generator.lokaltog.net/的主要靈感來源
2有一些特殊情況例外,包括如果git fetch
步驟失敗則什么都不做。
3 This is a bit of an oversimplification, as git merge
and git rebase
can take more than one hash ID, and for a case that is never used by git pull
, git rebase
also requires a branch name. 但是,為了由git pull
運行,它們最終在此處使用 hash ID。
origin
既是一個遙遠的地方,又是……嗯……但是為什么會這樣
git rebase -i origin
作品?
這是陡峭的學習曲線的另一部分讓你大吃一驚的地方。
Git 最終都是關於commits的。 存儲庫中的提交是使用 Git 的原因。 單個提交是有編號的,但數字本身是大的、丑陋的、看起來隨機的東西,完全不適合人類。 這些是 hash ID 或 Object ID,例如從git log
中溢出。 它們只有通過剪切和粘貼才能真正使用,所以我們基本上不使用它們:我們使用names 。
因此,Git 提供的不是一個而是兩個鍵值數據庫。 其中之一由 hash ID 索引,這就是 Git 如何訪問其提交和其他內部數據。 Git 放入一個 hash ID,並獲取提交或其他 object ID,其密鑰是特定的 Z0800FC577294C335945Z4。 當 object 是提交 object 時,它代表每個文件的完整快照,以您(或任何人)提交時的形式一直凍結。
但是,為了找到hash ID,Git 保留了第二個數據庫,其中的鍵是名稱:分支名稱、標簽名稱和其他類型的名稱。 分支名稱,如master
或main
、 dev
或develop
、 frab/jous
等,由您決定:您可以選擇任何您喜歡的名稱(盡管在 [0 -9a-f] 設置,因為“名稱” cafebabe
和badf00d
和deadcab
可以縮寫為 hash IDs)。 為了防止分支和標簽名稱相互碰撞,Git 實際上將refs/heads/
粘貼在每個分支名稱的前面,並將refs/tags/
粘貼在每個標簽名稱的前面。
Git 存儲在您的存儲庫中的名稱,以便記住其他一些 Git 存儲庫的分支名稱,是遠程跟蹤名稱(Git 調用這些遠程跟蹤分支名稱)並且實際上以refs/remotes/
為前綴,而不是origin/dev
,這些確實是refs/remotes/origin/dev
。
所有這些名稱,在這些不同的命名空間中,每個都擁有一個 hash ID。 這就是 Git 需要的全部內容,因為提交本身也持有其他提交 hash ID。 從一個提交中,Git 可以找到另一個。 從那里,Git 可以找到另一個提交,依此類推。 Git 簡單地將分支名稱定義為“此名稱包含要在此分支上稱為最新的提交的 hash ID”。
因此,如果您在某個分支main
上,則該名稱包含一些 hash ID H
,這是某個提交的 hash ID:
<-H <-- main
每個提交都包含一個先前提交 hash ID 的列表,通常只有一個條目長,以及所有文件的快照。 這是從H
出來的向后箭頭,在這里。 提交H
持有一些較早提交的 hash ID。 讓我們稱其為G
並將其繪制進去:
<-G <-H <-- main
當然, G
是一個帶有快照和另一個向后箭頭的提交,所以它必須指向某個更早的提交,它一遍又一遍地重復:
... <-F <-G <-H <-- main
那是一個 Git 分支。 要將提交添加到分支,我們按名稱“簽出”或“切換到”,使名稱成為當前分支名稱,對應的提交H
成為當前提交。
我們可以有多個名稱指向此提交。 讓我們畫幾個名字: main
和dev
以及origin/main
,這不是一個分支名稱,但仍然指向一個提交。 為了懶惰,我將停止在提交之間使用箭頭,但請記住 Git 只能向后工作,從不向前:
...--F--G--H <-- dev, main, origin/main
我們選擇一個分支——比如說dev
——切換到。 為了記住我們使用的是名稱dev
,我們將特殊名稱HEAD
附加到它:
...--F--G--H <-- dev (HEAD), main, origin/main
現在我們調整使用 Git 的方式——我不會在這里介紹,但索引或暫存區域(同一事物的兩個術語)至關重要——並最終做出一些新的提交。 我們稱之為I
的新提交具有新的唯一 hash ID 並向后指向現有提交H
,如下所示:
...--F--G--H
\
I
棘手的一點是 Git 在完成新提交后立即更新當前分支名稱I
。 其他名稱均未更新,因此它們仍指向H
:
...--F--G--H <-- main, origin/main
\
I <-- dev (HEAD)
提交I
現在是dev
上的最新提交。 通過H
進行的提交仍在dev
上,並且也繼續在main
上。 特殊名稱HEAD
仍然附加到dev
,我們當前的提交現在是提交I
。 提交H
仍然存在(並且,對於 Git 的散列方案至關重要的是,它完全沒有受到影響:這就是為什么箭頭都是 go 向后,而不是向前)。
好吧,但是——那又怎樣? 好吧,正如我之前所說,Git 是關於提交的。 當您給 Git 一個分支名稱時,大多數情況下,它會通過找出名稱指向的位置很快將該名稱轉換為 hash ID。 ( git switch
和git checkout
命令在這里是不尋常的,因為它們也必須記住名稱,這樣當它們完成時你就可以“打開”該分支。)你,即git rev-parse
。 如果我們給git rev-parse
一些分支名稱,我們可以看到它的實際作用:
$ git rev-parse master
5d01301f2b865aa8dba1654d3f447ce9d21db0b5
$ git rev-parse diff-merge-base
fa1c8acabf0d5649baf87f549d67426d14255e0f
它也可以解析標簽名稱和遠程跟蹤名稱,並且使用--symbolic-full-name
它可以告訴我們每個名稱的完整拼寫是什么:
$ git rev-parse --symbolic-full-name v2.35.1
refs/tags/v2.35.1
$ git rev-parse --symbolic-full-name origin/master
refs/remotes/origin/master
$ git rev-parse origin/master
5d01301f2b865aa8dba1654d3f447ce9d21db0b5
如果我們單獨給它origin
會發生什么?
$ git rev-parse origin
5d01301f2b865aa8dba1654d3f447ce9d21db0b5
$ git rev-parse --symbolic-full-name origin
refs/remotes/origin/master
嗯,這有點奇怪,不是嗎? 讓我們看一下gitrevisions 文檔,它非常重要,並且巧妙地隱藏在一堆 1000 個基本上不可讀的手冊頁中:
指定修訂
...
<refname> eg, master , heads/master , refs/heads/master
... a<refname>
通過采用以下規則中的第一個匹配來消除歧義:
- 如果
$GIT_DIR/<refname>
存在,那就是你的意思(這通常只對HEAD
、FETCH_HEAD
、ORIG_HEAD
、MERGE_HEAD
和CHERRY_PICK_HEAD
);- 否則,
refs/<refname>
如果存在;- 否則,
refs/tags/<refname>
如果存在;- 否則,如果存在,則為
refs/heads/<refname>
;- 否則,
refs/remotes/<refname>
如果存在;- 否則,如果存在,則為
refs/remotes/<refname>/HEAD
。
正是這個六步規則使名稱縮寫起作用。 我們寫:
git rebase master
和 Git 嘗試將master
作為.git
中的文件(步驟 1),但這不存在,所以 Git 繼續嘗試refs/master
作為名稱。 這也不存在,因此 Git 嘗試將refs/heads/master
作為名稱(步驟 3)。 那個確實存在,無論如何在這個存儲庫中,所以它解析為 hash ID 並且修訂指定已完成。
如果我們使用origin/master
,步驟 5 會找到它,因為refs/remotes/origin/master
存在(使用git for-each-ref
轉儲 ref 表,並查看它確實存在)。 如果我們使用origin
它似乎根本不是一個 ref-name——第6步會找到它,因為refs/remotes/origin/HEAD
存在。
現在, HEAD
以及相應的refs/remotes/origin/HEAD
HEAD——是一個特例:它是一個符號引用,在 Git 中類似於 Unix/Linux 文件系統中的符號鏈接。 (In fact, in early Git implementations, it simply was a symbolic link. That does not work well on Windows though, so now it's a file with contents.) The git for-each-ref
command expands the link by default, but git branch -r
沒有,所以這是看到這一點的一種方式。
這一切的結論是:
origin/HEAD
是任何分支的符號引用,它是origin
中的HEAD
,通常是master
或main
;origin
本身是遠程的(由git fetch
使用),或可通過 gitrevisions 的第 6 步解析(如大多數其他 Git 命令使用);git rebase -i origin
通過origin/HEAD
和步驟 6 解決它; 但git pull origin master
根本不使用第 6 步:字符串origin
只是一個remote ,字符串master
通過遠程跟蹤名稱映射成為origin/master
(在這種特殊情況下git pull
實際上回避了這一切因為它使用的是.git/FETCH_HEAD
文件機制,它早於所有這些東西和 go 通過稍微不同的代碼路徑)。 git pull
命令將它的大部分標志和 arguments 傳遞給git fetch
,除了它傳遞給第二個命令的一些標志,以及它自己使用的一些標志。 由於歷史......錯誤,它非常復雜? 想法? 概念? 無論如何,Git過去工作方式的歷史,必須在接下來的 3 億年或其他任何時間里保存在琥珀中。 (說真的,Git 的人非常重視兼容性本身,並盡量不破壞現有的使用和工作流程。)
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.