Git --local-branching-on-the-cheap

6.7 Git 工具 - 子樹合併

子樹合併

現在你已經看到了子模組系統的麻煩之處,讓我們來看一下解決相同問題的另一途徑。當 Git 歸併(merge)時,它會檢查需要歸併的內容然後選擇一個合適的歸併策略。如果你歸併的分支是兩個,Git使用一個「遞迴 (recursive)」策略。如果你歸併的分支超過兩個,Git採用「章魚」策略。這些策略是自動選擇的,因為遞迴策略可以處理複雜的三路歸併情況——比如多於一個共同祖先——但是它只能處理兩個分支的歸併。章魚歸併可以處理多個分支,但是必須更加小心以避免衝突帶來的麻煩,因此它被選中作為歸併兩個以上分支的預設策略。

實際上,你也可以選擇其他策略。其中的一個就是「子樹歸併 (subtree merge)」,你可以用它來處理子專案問題。這裡你會看到如何換用子樹歸併的方法來實現前一節裡所做的 rack 的嵌入。

子樹歸併的想法是你擁有兩個專案,其中一個專案映射到另外一個專案的子目錄中,反過來也一樣。當你指定一個子樹歸併,Git 可以聰明地探知其中一個是另外一個的子樹從而實現正確的歸併——這相當神奇。

首先你將 Rack 應用加入到專案中。你將 Rack 專案當作你專案中的一個遠端參照,然後將它 check out 到它自身的分支:

$ git remote add rack_remote git@github.com:schacon/rack.git
$ git fetch rack_remote
warning: no common commits
remote: Counting objects: 3184, done.
remote: Compressing objects: 100% (1465/1465), done.
remote: Total 3184 (delta 1952), reused 2770 (delta 1675)
Receiving objects: 100% (3184/3184), 677.42 KiB | 4 KiB/s, done.
Resolving deltas: 100% (1952/1952), done.
From git@github.com:schacon/rack
 * [new branch]      build      -> rack_remote/build
 * [new branch]      master     -> rack_remote/master
 * [new branch]      rack-0.4   -> rack_remote/rack-0.4
 * [new branch]      rack-0.9   -> rack_remote/rack-0.9
$ git checkout -b rack_branch rack_remote/master
Branch rack_branch set up to track remote branch refs/remotes/rack_remote/master.
Switched to a new branch "rack_branch"

現在在你的 rack_branch 分支中就有了 Rack 專案的根目錄,而你自己的專案在 master 分支中。如果你先 check out 其中一個然後另外一個,你會看到它們有不同的專案根目錄:

$ ls
AUTHORS        KNOWN-ISSUES   Rakefile      contrib        lib
COPYING        README         bin           example        test
$ git checkout master
Switched to branch "master"
$ ls
README

要將 Rack 專案當作子目錄拉取到你的 master 專案中。你可以在 Git 中用 git read-tree 來實現。你會在第9章學到更多與 read-tree 和它的朋友相關的東西,目前你會知道它讀取一個分支的根目錄樹到當前的暫存區和工作目錄。你只要切換回你的 master 分支,然後拉取 rack 分支到你主專案的 master 分支的 rack 子目錄:

$ git read-tree --prefix=rack/ -u rack_branch

當你提交的時候,看起來就像你在那個子目錄下擁有全部 Rack 的檔案——就像你從一個 tarball 裡拷貝的一樣。有意思的是你可以比較容易地歸併其中一個分支的變更到另外一個。因此,如果 Rack 專案更新了,你可以通過切換到那個分支並執行拉取來獲得上游的變更:

$ git checkout rack_branch
$ git pull

然後,你可以將那些變更歸併回你的 master 分支。你可以使用 git merge -s subtree,它會工作的很好;但是 Git 同時會把歷史歸併到一起,這可能不是你想要的。為了拉取變更並預置提交說明,需要在 -s subtree 策略選項的同時使用 --squash--no-commit 選項。

$ git checkout master
$ git merge --squash -s subtree --no-commit rack_branch
Squash commit -- not updating HEAD
Automatic merge went well; stopped before committing as requested

所有 Rack 專案的變更都被歸併進來,而且可以進行本地提交。你也可以做相反的事情——在你主分支的 rack 目錄裡進行變更然後歸併回 rack_branch 分支,然後將它們提交給維護者或者推送到上游。

為了得到 rack 子目錄和你 rack_branch 分支的區別——以決定你是否需要歸併它們——你不能使用一般的 diff 命令。而是對你想比較的分支執行 git diff-tree

$ git diff-tree -p rack_branch

或者,為了比較你的 rack 子目錄和伺服器上你拉取時的 master 分支,你可以執行

$ git diff-tree -p rack_remote/master