如何用 Git 合並兩個庫,並保留提交歷史


轉載自 https://segmentfault.com/a/1190000000678808

 

背景

    一個中型規模項目,開始規划時就打算采用 C/S 架構,后端是單純的 API 服務,前端在 Web 上搞一個 SPA,之后再搞其他端也就順理成章了。只可以第一次弄沒經驗,有些細節最初沒有考慮到。

    創建項目的時候前后端真是完全分離的,分成了兩個目錄,創建了兩個 repos。一開始只有一個人干的時候倒也沒什么,開兩個窗口切來切去也就罷了,后來一是部署起來麻煩,二來主要是其他開發者加入后,代碼的版本管理、提交、合並、審核等等等等都變得越來越繁瑣。

后來一想:架構上分離而已,干嘛非要兩個目錄兩個 repos?真是自找麻煩!於是就開始考慮整合。

要求

    把兩個目錄並成一個倒不難,但是要完整保留雙方的歷史記錄就有些麻煩了,這也是唯一一個必須要實現的目標。

過程

    首先為了便於描述,約定整合前兩個目錄分別叫做 frontendbackend,合並后的結構與名稱應當如下:

- project/      => 即最開始的 frontend,整合完后更名
- .gitignore    => 合並兩個 repos 的忽略文件
- .git/         => 最終僅余一個 repo
+ client/       => 對應 frontend
+ server/       => 對應 backend

    以下步驟是以 frontend 為基點,把 backend 移進來,實際上反過來也是一樣的,自行替換對應的名稱即可。在開始之前先清理兩個repos 里的工作記錄,該提交的提交,該備份的備份,保持干凈

1. $ [~] cd frontend
2. $ [frontend] git remote add -f backend /fullpath/to/backend
3. $ [frontend] git merge --strategy ours --no-commit backend/master
4. $ [frontend] mkdir -p server
5. $ [frontend] git read-tree --prefix=server/ -u backend/master
6. $ [frontend] git commit --message '完成 backend 的遷移,新目錄為 server'
7. $ [frontend] mkdir -p client
8. # 拷貝 frontend 的原始項目文件(除了 .git/ 和 .gitignore 以外)至 client/
9. $ [frontend] cd ..; mv frontend/ project/; cd project
10. $ [project] cat server/.gitignore >> .gitignore
11. # 整理合並后的 .gitignore,修復其中的路徑缺失並保存;修復各種項目依賴的缺失,本地測試。
12. $ [project] git add --all; git commit --message '遷移整合完成!'

以上是完整的步驟先列出來方便參考,下面做一個詳細的解釋。

    整個過程中主要用到的工具是 mergeread-tree,前者用於合並歷史記錄並且中斷在最后提交之前,所產生的文件沖突不會被寫入硬盤;然后利用后者重寫整個文件樹並把讀取到的內容(讀取的目標是 backend)寫入新的路徑下。最后提交以結束合並。

    第2步里,我們把 backend 作為 remote server 添加到 frontend 庫中。-f 的作用是在添加后立刻 fetch。要注意一定得使用絕對路徑來引用 backend 庫。

    第3步里--strategy ours 比較難以理解,且聽我詳細道來:一般來說當合並兩個文件樹時,如果遇到沖突我們是需要手動去解決它的,但是目前我們要做的不是解決沖突,而是在引入 backend 歷史記錄的前提下完整保留 frontend 的內容。沖突肯定是會有的,即使兩個不同的項目也是如此,比方說兩邊都有 README.mdapp/config/ 等文件或目錄,但是我們不關心沖突,我們只要保留frontend 的文件樹並且把 backend 的歷史記錄合並進來。

--strategy ours 會完成全部的合並解析,但是所有的沖突都以“我”為准,不允許外來的沖突覆蓋“我”的文件內容。最終的結果就是:

  1. backend 的歷史記錄被合並到 frontend 的歷史記錄中
  2. backend 的文件樹被讀取並和 frontend 的文件樹比對進行沖突解析:
    • 如果發現沖突,以 frontend 為准,丟棄所有內容變更
    • 沒沖突的則保留(但是我們也不要的,見后面的內容)

這也是后面緊接着使用 --no-commit 的原因,該選項會在合並解析完成后中斷,停留在最后的提交步驟之前。我們知道,只要你還沒commit,那么 merge 的結果就暫時保存在緩存區中,只有完成提交步驟合並才算徹底完成(文件樹被正式改變)。這就給我們一個機會來重新讀取 backend 的文件樹,並改寫其保存的位置。

    第4步創建目標子目錄(很重要!)。

    第5步開始 read-tree 了,--prefix 用於指定文件樹讀取后保存的路徑,相對於當前路徑並且一定要追加 /-u 是說在讀取后更新index,使得 working treeindex 保持同步。如果你不小心忘了加 -u,可以在這一步之后執行 git add --update,一樣的效果。

    這一步在背后有些細節比較抽象,之前的 merge 也曾讀取過 backend 的文件樹,但經過沖突解析之后已經面目全非,分析如下:

  • 有沖突的被丟棄,因此一部分文件/目錄其實已經不存在了
  • 沒沖突的被保留,但是路徑還在 frontend 的根路徑下

經過再次 read-tree,上面的“遺跡”得以修復,結果如下:

  • 有沖突的因為已被丟棄,所以直接從本次讀取中獲得,且路徑前面追加 --prefix 選項的值
  • 沒沖突的雖然被保留,但是由於本次讀取追加了-- prefix,所以它們的路徑也被改變,相當於在緩存里做了一次 git mv

好了,重點就是這些,之后的步驟都很尋常,只要小心操作就沒什么難理解的。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM