通常,當我們必須在當前分支(mybranch)上合并其他一些分支(branch-to-merge)時,我們會這樣做:
git checkout mybranch
git pull origin branch-to-merge
我理解它的作業方式是它將 mybranch 指標移動到與分支合并頭相同的提交。如果是線性場景,一切順利,否則我們需要解決合并沖突。
但是如果分支到合并在 mybranch 后面,它會如何發揮作用?我基本上想將分支到合并中的更改(它是滯后的)集成到我當前的功能分支中,以利用分支到合并中所做的代碼更改。
uj5u.com熱心網友回復:
TL;DR:什么也沒有發生(Git 說Already up to date.并在成功退出時停止)。
長
您的理解并非完全錯誤,但缺少一兩個關鍵細節,這會導致問題:
git checkout mybranch git pull origin branch-to-merge... 將 mybranch 指標移動到與分支合并頭相同的提交。
讓我們在這里精確一點。該git checkout mybranch步驟:
- 從當前提交中洗掉任何檔案(在 Git 的索引aka staging area 中記得);
- 從名稱指示的提交中提取檔案
mybranch;和 - 使
mybranch當前分支。
(或者,當然,它可能會因各種錯誤情況而失敗,但我們可以假設這些不會發生。它也可以在保留未提交的作業的同時成功,如當前分支上有未提交的更改時檢查另一個分支中所述:當它可以跳過特定檔案的洗掉和替換時發生,Git 通常出于速度原因嘗試這樣做,無論這些檔案中是否有未提交的作業)。
完成此結帳后,然后運行git pull. 該pull命令是另外兩個 Git 命令的組合:
git pull運行git fetch。在舊版本的 Git 中,它實際上是這樣做的(它是一個 shell 腳本,它實際上使用各種引數運行git fetch)。在目前的Git中,這是內置在pull程式中的,但是效果是一樣的。此步驟獲取origin您的存盤庫缺少的任何提交(一個單獨的 Git 存盤庫)。origin/branch-to-merge在正常情況下,此步驟還會更新您自己的存盤庫中的名稱。git pull然后運行您撰寫的第二個命令來運行。如果您尚未設定任何內容,則默認為 usinggit merge,但您可以將其設定為 rungit rebase。與 fetch 步驟一樣,舊腳本確實運行了這些;新的 C 代碼內置了它們,但效果是一樣的。傳遞給git mergeor的引數git rebase有點復雜,但是如果我們假設你在git merge這里使用,并且其他東西都是正常的,我們就會得到運行的效果git merge origin/branch-to-merge(默認提交訊息除外)。但您也可以git merge --no-ff在此處或現在使用git merge --ff-only. 如果您將其git rebase用作第二個命令,則圖片會更加復雜。
如果是線性場景,一切順利,否則我們需要解決合并沖突。
這有許多棘手的細節和極端情況。該git merge命令在多種場景下運行,并帶有各種引數 ( --no-ff, --ff-only),每個引數都有不同的效果。但總的來說,我們可以將合并描述為這樣作業:
首先,Git 確保我們有一個“干凈”的作業樹(一些合并情況不會檢查,但如果您使用這些奇怪的合并變體之一,確保自己就是這種情況是一個非常好的主意)。Git 然后使用當前分支名稱 from或原始哈希 ID from來定位當前提交哈希 ID 。如果發生合并沖突,這就是“HEAD”或“我們的”提交。
HEADHEADGit 還會將引數指定的提交定位到
git merge. 也就是說,origin/branch-to-merge轉換為原始提交哈希 ID。Git 找到了這個特定的提交。Git 然后使用提交圖計算兩個提交的合并基數(請參閱有向無環圖的最低公共祖先)。就確定合并結果而言,這是在某些方面最重要的提交。
我們可以通過多種方式繪制合并基礎。例如,假設我們有這個:
o--L <-- mybranch (HEAD)
/
...--o--*
\
o--R <-- origin/branch-to-merge
Here, the name mybranch locates commit L, the "left side" or "local" or HEAD or ours commit. The name origin/branch-to-merge locates commit R: the other, or "right side" or "remote" or --theirs commit. The commit marked * is the best common ancestor, so commit * is the merge base.
This kind of merge requires a true merge. It may produce a merge conflict, but if it does not, and if a true merge is permitted, the result of this merge is a new merge commit M:
o--L
/ \
...--o--* M <-- mybranch (HEAD)
\ /
o--R <-- origin/branch-to-merge
Because this commit is made while "on" mybranch (in that git status says on branch mybranch), the branch name mybranch now points to merge commit M.
If there are merge conflicts, Git stops in the middle of the merge, with all three commits read into Git's index, and the merge partly resolved and partly unresolved. Your job as the human operating Git is to finish the merge, which cleans up the mess Git left behind; or you may choose to run git merge --abort to throw away the partial result, cleaning up the mess by, in essence, resetting to commit L. (This usually goes quite badly if you start a "dirty" merge and then use git merge --abort, which is why you should not start a "dirty" merge in the first place.)
If a true merge is required and you specified --ff-only, git merge does not even start the merge: it just says that a fast-forward is impossible and terminates with an error.
Another scenario is illustrated by this drawing:
...--o--L <-- mybranch (HEAD)
\
o--R <-- origin/branch-to-merge
Here, the "merge base"—the best shared commit that is reachable from both commits L and R—is commit L. This is your linear scenario: Git can simply "move the branch name forward" while also checking out commit R, resulting in:
...--o--L--o--R <-- mybranch (HEAD), origin/branch-to-merge
This is the default action if you specified --ff-only or did not use --no-ff. However, if you specified --no-ff, Git will go ahead and do a full merge anyway:
...--o--L------M <-- mybranch (HEAD)
\ /
o--R <-- origin/branch-to-merge
Commit M is a merge commit as before. The snapshot for commit M will match that for commit R, since the merge commit snapshot is made by combining the changes from the merge base (L) to each tip commit (L and R respectively). The changes from L to L are empty, so this produces the changes found from L to R. Git applies those changes to the snapshot in the merge base—i.e., in L—which produces a snapshot that matches commit R. Git then commits the result, as there were no merge conflicts.
But how does it play out if branch-to-merge is behind mybranch?
Note that in one case we already drew:
o--L <-- mybranch (HEAD)
/
...--o--*
\
o--R <-- origin/branch-to-merge
it is the case that origin/branch-to-merge is behind mybranch. It's just that, in spite of being behind by two commits, origin/branch-to-merge is also ahead by two commits. That is, there are two commits, those along the bottom line, that are "on" (reachable from) commit R that are not "on" (not reachable from) commit L.
Let's draw this other case though:
...--o--R <-- origin/branch-to-merge
\
o--L <-- mybranch (HEAD)
Here, the merge base of L and R is R. Merging is not possible: the diff from R to R is empty, and applying the diff from R to L produces the snapshot in L. Git could still do the same kind of "forced merge" it does when R is strictly ahead of L, but Git won't do that. Instead it just says that it is already up to date.
The bottom line
The merge command:
- locates the commits to merge (let's call them
LandRfor the two-commit case); - locates the merge base (let's call this
B); and - uses this to drive the rest of the action.
For a standard two-head merge with recursive or resolve, there are three possibilities, considered in this order:
B=R: emit "up to date" message and terminate with success.B=L: consider doing a fast-forward instead of a merge. If allowed, check out commitR, update the stored hash ID in the current branch, and terminate with success.- Otherwise, if allowed (no
--ff-only), do a full merge. If this is successful (and neither--no-commitnor--squashwere specified), make a new merge commit, otherwise stop. For--squash, don't record the hash ID ofRinMERGE_HEAD; for other merges, do record the hash ID ofRinMERGE_HEAD.
In the special case that B = L = R, we do the B=R test first and say "up to date". The --no-ff option simply forces the middle test to fail so that we go on to the third case.
The --squash option ensures that merge commit M has only a single parent. (It does this by terminating the merge early, as if -n had been specified, which is kind of stupid: you could just run git merge -n --squash if you really wanted that, and it could just make the final commit as a non-merge commit when not using -n, and then declare the merge done.) The -n option ensures that Git doesn't make the final commit right away, which allows you to create an evil merge if you wish.
How to see the merge base
If you're good at eyeballing Git graphs (see Pretty Git branch graphs) you may be able to spot the merge base just by looking, but a lot of graphs are horribly tangled. You can run git merge-base --all:
git fetch
git merge-base --all mybranch origin/branch-to-merge
The second command spits out hash IDs. Ideally, it spits out just one hash ID: that one hash ID is the merge base, and all goes simply from there.
If this prints two or more hash IDs, you have a complex merge. The default (-s recursive, or now, -s ort) strategies will merge the merge bases, creating a new but temporary commit from the result, and then use the result as the merge base. This is simple in theory, but hard to comprehend and even harder to describe well. It's also pretty rare, so you probably won't encounter it.
轉載請註明出處,本文鏈接:https://www.uj5u.com/qukuanlian/351146.html
標籤:混帐
