<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="zh-Hant-TW">
	<id>https://jiva.dila.edu.tw/index.php?action=history&amp;feed=atom&amp;title=Pro_Git_3.6_%E8%A1%8D%E5%90%88</id>
	<title>Pro Git 3.6 衍合 - 修訂歷史</title>
	<link rel="self" type="application/atom+xml" href="https://jiva.dila.edu.tw/index.php?action=history&amp;feed=atom&amp;title=Pro_Git_3.6_%E8%A1%8D%E5%90%88"/>
	<link rel="alternate" type="text/html" href="https://jiva.dila.edu.tw/index.php?title=Pro_Git_3.6_%E8%A1%8D%E5%90%88&amp;action=history"/>
	<updated>2026-05-05T14:07:37Z</updated>
	<subtitle>本 Wiki 上此頁面的修訂歷史</subtitle>
	<generator>MediaWiki 1.39.1</generator>
	<entry>
		<id>https://jiva.dila.edu.tw/index.php?title=Pro_Git_3.6_%E8%A1%8D%E5%90%88&amp;diff=538&amp;oldid=prev</id>
		<title>imported&gt;Ray：​新頁面: 把一個分支整合到另一個分支的辦法有兩種：merge（合併） 和 rebase（衍合）。在本章我們會學習什麼是衍合，如何使用衍合，為什麼衍合操作...</title>
		<link rel="alternate" type="text/html" href="https://jiva.dila.edu.tw/index.php?title=Pro_Git_3.6_%E8%A1%8D%E5%90%88&amp;diff=538&amp;oldid=prev"/>
		<updated>2011-05-25T03:30:33Z</updated>

		<summary type="html">&lt;p&gt;新頁面: 把一個分支整合到另一個分支的辦法有兩種：merge（合併） 和 rebase（衍合）。在本章我們會學習什麼是衍合，如何使用衍合，為什麼衍合操作...&lt;/p&gt;
&lt;p&gt;&lt;b&gt;新頁面&lt;/b&gt;&lt;/p&gt;&lt;div&gt;把一個分支整合到另一個分支的辦法有兩種：merge（合併） 和 rebase（衍合）。在本章我們會學習什麼是衍合，如何使用衍合，為什麼衍合操作如此富有魅力，以及我們應該在什麼情況下使用衍合。&lt;br /&gt;
&lt;br /&gt;
=衍合基礎=&lt;br /&gt;
&lt;br /&gt;
請回顧之前有關合併的一節（見圖 3-27），你會看到開發進程分叉到兩個不同分支，又各自提交了更新。&lt;br /&gt;
&lt;br /&gt;
[[圖片:pro-git-3-27.png]]&amp;lt;br&amp;gt;&lt;br /&gt;
圖 3-27. 最初分叉的提交歷史。&lt;br /&gt;
&lt;br /&gt;
之前介紹過，最容易的整合分支的方法是 merge 命令，它會把兩個分支最新的快照（C3 和 C4）以及二者最新的共同祖先（C2）進行三方合併。如圖 3-28 所示：&lt;br /&gt;
&lt;br /&gt;
[[圖片:pro-git-3-28.png]]&amp;lt;br&amp;gt; &lt;br /&gt;
圖 3-28. 通過合併一個分支來整合分叉了的歷史。&lt;br /&gt;
&lt;br /&gt;
其實，還有另外一個選擇：你可以把在 C3 裡產生的變化補丁重新在 C4 的基礎上打一遍。在 Git 裡，這種操作叫做 衍合（rebase）。有了 rebase 命令，就可以把在一個分支裡提交的改變在另一個分支裡重放一遍。&lt;br /&gt;
&lt;br /&gt;
在這個例子裡，可以運行下面的命令：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;XML&amp;quot;&amp;gt;&lt;br /&gt;
$ git checkout experiment&lt;br /&gt;
$ git rebase master&lt;br /&gt;
First, rewinding head to replay your work on top of it...&lt;br /&gt;
Applying: added staged command&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
它的原理是回到兩個分支（你所在的分支和你想要衍合進去的分支）的共同祖先，提取你所在分支每次提交時產生的差異（diff），把這些差異分別保存到暫存檔案裡，然後從當前分支轉換到你需要衍合入的分支，依序施用每一個差異補丁檔。圖 3-29 演示了這一過程：&lt;br /&gt;
&lt;br /&gt;
[[圖片:pro-git-3-29.png]]&amp;lt;br&amp;gt;&lt;br /&gt;
圖 3-29. 把 C3 裡產生的改變衍合到 C4 中。&lt;br /&gt;
&lt;br /&gt;
現在，你可以回到 master 分支然後進行一次快進合併(fast-forward merge)（見圖 3-30）：&lt;br /&gt;
&lt;br /&gt;
[[圖片:pro-git-3-30.png]]&amp;lt;br&amp;gt;&lt;br /&gt;
圖 3-30. master 分支的快進。&lt;br /&gt;
&lt;br /&gt;
現在，合併後的 C3（即現在的 C3’）所指的快照，同三方合併例子中的 C5 所指的快照內容一模一樣了。最後整合得到的結果沒有任何區別，但衍合能產生一個更為整潔的提交歷史。如果視察一個衍合過的分支的歷史記錄，看起來更清楚：仿佛所有修改都是先後進行的，儘管實際上它們原來是同時發生的。&lt;br /&gt;
&lt;br /&gt;
你可以經常使用衍合，確保在遠端分支裡的提交歷史更清晰。比方說，某些項目自己不是維護者，但想幫點忙，就應該盡可能使用衍合：先在一個分支裡進行開發，當準備向主專案提交補丁的時候，再把它衍合到 origin/master 裡面。這樣，維護者就不需要做任何整合工作，只需根據你提供的倉庫位址作一次快進，或者採納你提交的補丁。&lt;br /&gt;
&lt;br /&gt;
請注意，合併結果中最後一次提交所指向的快照，無論是通過一次衍合還是一次三方合併，都是同樣的快照內容，只是提交的歷史不同罷了。衍合按照每行改變發生的次序重演發生的改變，而合併是把最終結果合在一起。&lt;br /&gt;
&lt;br /&gt;
=更多有趣的衍合=&lt;br /&gt;
&lt;br /&gt;
你還可以在衍合分支以外的地方衍合。以圖 3-31 的歷史為例。你創建了一個特性分支 server 來給伺服器端代碼添加一些功能，然後提交 C3 和 C4。然後從 C3 的地方再增加一個 client 分支來對用戶端代碼進行一些修改，提交 C8 和 C9。最後，又回到 server 分支提交了 C10。&lt;br /&gt;
&lt;br /&gt;
[[圖片:pro-git-3-31.png]]&amp;lt;br&amp;gt;&lt;br /&gt;
圖 3-31. 從一個特性分支裡再分出一個特性分支的歷史。&lt;br /&gt;
&lt;br /&gt;
假設在接下來的一次軟體發佈中，你決定把用戶端的修改先合併到主線中，而暫緩併入服務端軟體的修改（因為還需要進一步測試）。你可以僅提取對用戶端的改變（C8 和 C9），然後通過使用 git rebase 的 --onto 選項來把它們在 master 分支上重演：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;XML&amp;quot;&amp;gt;&lt;br /&gt;
$ git rebase --onto master server client&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
這基本上等於在說「檢出 client 分支，找出 client 分支和 server 分支的共同祖先之後的變化，然後把它們在 master 上重演一遍」。是不是有點複雜？不過它的結果，如圖 3-32 所示，非常酷：&lt;br /&gt;
&lt;br /&gt;
[[圖片:pro-git-3-32.png]]&amp;lt;br&amp;gt;&lt;br /&gt;
圖 3-32. 衍合一個特性分支上的另一個特性分支。&lt;br /&gt;
&lt;br /&gt;
現在可以快進 master 分支了（見圖 3-33）：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;XML&amp;quot;&amp;gt;&lt;br /&gt;
$ git checkout master&lt;br /&gt;
$ git merge client&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[圖片:pro-git-3-33.png]]&amp;lt;br&amp;gt;&lt;br /&gt;
圖 3-33. 快進 master 分支，使之包含 client 分支的變化。&lt;br /&gt;
&lt;br /&gt;
現在你決定把 server 分支的變化也包含進來。可以直接把 server 分支衍合到 master 而不用手工轉到 server 分支再衍合。git rebase [主分支] [特性分支] 命令會先檢出特性分支 server，然後在主分支 master 上重演：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;XML&amp;quot;&amp;gt;&lt;br /&gt;
$ git rebase master server&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
於是 server 的進度應用到 master 的基礎上，如圖 3-34：&lt;br /&gt;
&lt;br /&gt;
[[圖片:pro-git-3-34.png]]&amp;lt;br&amp;gt; &lt;br /&gt;
圖 3-34. 在 master 分支上衍合 server 分支。&lt;br /&gt;
&lt;br /&gt;
然後快進主分支 master：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;XML&amp;quot;&amp;gt;&lt;br /&gt;
$ git checkout master&lt;br /&gt;
$ git merge server&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
現在 client 和 server 分支的變化都被整合了，不妨刪掉它們，把你的提交歷史變成圖 3-35 的樣子：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;XML&amp;quot;&amp;gt;&lt;br /&gt;
$ git branch -d client&lt;br /&gt;
$ git branch -d server&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[圖片:pro-git-3-35.png]]&amp;lt;br&amp;gt;&lt;br /&gt;
圖 3-35. 最終的提交歷史&lt;br /&gt;
&lt;br /&gt;
=衍合的風險=&lt;br /&gt;
&lt;br /&gt;
呃，奇妙的衍合也不是完美無缺的，一句話可以總結這點：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;永遠不要衍合那些已經推送到公共倉庫的更新。&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
如果你遵循這條金科玉律，就不會出差錯。否則，群眾會仇恨你，你的朋友和家人也會嘲笑你、唾棄你。&lt;br /&gt;
&lt;br /&gt;
在衍合的時候，實際上拋棄了一些現存的 commit 而創造了一些類似但不同的新 commit。如果你把commit 推送到某處然後其他人下載並在其基礎上工作，然後你用 git rebase 重寫了這些commit 再推送一次，你的合作者就不得不重新合併他們的工作，這樣當你再次從他們那裡獲取內容的時候事情就會變得一團糟。&lt;br /&gt;
&lt;br /&gt;
下面我們用一個實際例子來說明為什麼公開的衍合會帶來問題。假設你從一個中央伺服器克隆然後在它的基礎上做了一些開發，提交歷史類似圖 3-36：&lt;br /&gt;
&lt;br /&gt;
[[圖片:pro-git-3-36.png]]&amp;lt;br&amp;gt;&lt;br /&gt;
圖 3-36. 克隆一個倉庫，在其基礎上工作一番。&lt;br /&gt;
&lt;br /&gt;
現在，其他人進行了一些包含一次合併的工作（得到結果 C6），然後把它推送到了中央伺服器。你獲取了這些資料並把它們合併到你本地的開發進程裡，讓你的歷史變成類似圖 3-37 這樣：&lt;br /&gt;
&lt;br /&gt;
[[圖片:pro-git-3-37.png]]&amp;lt;br&amp;gt;&lt;br /&gt;
圖 3-37. 獲取更多提交，併入你的開發進程。&lt;br /&gt;
&lt;br /&gt;
接下來，那個推送 C6 上來的人決定用衍合取代那次合併；他們用 git push --force 覆蓋了伺服器上的歷史，得到 C4’。然後你再從伺服器上獲取更新：&lt;br /&gt;
&lt;br /&gt;
[[圖片:pro-git-3-38.png]]&amp;lt;br&amp;gt;&lt;br /&gt;
圖 3-38. 有人推送了衍合過的 C4’，丟棄了你作為開發基礎的 C6。&lt;br /&gt;
&lt;br /&gt;
這時候，你需要再次合併這些內容，儘管之前已經做過一次了。衍合會改變這些 commit 的 SHA-1 校驗值，這樣 Git 會把它們當作新的 commit，然而這時候在你的提交歷史早就有了 C4 的內容（見圖 3-39）:&lt;br /&gt;
&lt;br /&gt;
[[圖片:pro-git-3-39.png]]&amp;lt;br&amp;gt;&lt;br /&gt;
圖 3-39. 你把相同的內容又合併了一遍，生成一個新的提交 C8。&lt;br /&gt;
&lt;br /&gt;
你遲早都是要併入其他協作者提交的內容的，這樣才能保持同步。當你做完這些，你的提交歷史裡會同時包含 C4 和 C4’，兩者有著不同的 SHA-1 校驗值，但卻擁有一樣的作者日期與提交說明，令人費解！更糟糕的是，當你把這樣的歷史推送到伺服器，會再次把這些衍合的提交引入到中央伺服器，進一步迷惑其他人。&lt;br /&gt;
&lt;br /&gt;
如果&amp;lt;b&amp;gt;把衍合當成一種在推送之前清理提交歷史的手段&amp;lt;/b&amp;gt;，而且&amp;lt;b&amp;gt;僅僅衍合那些永遠不會公開的 commit&amp;lt;/b&amp;gt;，那就不會有任何問題。如果衍合那些已經公開的 commit，而與此同時其他人已經用這些 commit 進行了後續的開發工作，那你有得麻煩了。&lt;/div&gt;</summary>
		<author><name>imported&gt;Ray</name></author>
	</entry>
</feed>