轉載自 https://segmentfault.com/a/1190000000678808
背景
一個中型規模項目,開始規划時就打算采用 C/S 架構,后端是單純的 API 服務,前端在 Web 上搞一個 SPA,之后再搞其他端也就順理成章了。只可以第一次弄沒經驗,有些細節最初沒有考慮到。
創建項目的時候前后端真是完全分離的,分成了兩個目錄,創建了兩個 repos。一開始只有一個人干的時候倒也沒什么,開兩個窗口切來切去也就罷了,后來一是部署起來麻煩,二來主要是其他開發者加入后,代碼的版本管理、提交、合並、審核等等等等都變得越來越繁瑣。
后來一想:架構上分離而已,干嘛非要兩個目錄兩個 repos?真是自找麻煩!於是就開始考慮整合。
要求
把兩個目錄並成一個倒不難,但是要完整保留雙方的歷史記錄就有些麻煩了,這也是唯一一個必須要實現的目標。
過程
首先為了便於描述,約定整合前兩個目錄分別叫做 frontend
和 backend
,合並后的結構與名稱應當如下:
- project/ => 即最開始的 frontend,整合完后更名 |
以下步驟是以 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 '遷移整合完成!'
以上是完整的步驟先列出來方便參考,下面做一個詳細的解釋。
整個過程中主要用到的工具是 merge 和 read-tree,前者用於合並歷史記錄並且中斷在最后提交之前,所產生的文件沖突不會被寫入硬盤;然后利用后者重寫整個文件樹並把讀取到的內容(讀取的目標是 backend
)寫入新的路徑下。最后提交以結束合並。
第2
步里,我們把 backend
作為 remote server 添加到 frontend
庫中。-f
的作用是在添加后立刻 fetch
。要注意一定得使用絕對路徑來引用 backend
庫。
第3
步里,--strategy ours
比較難以理解,且聽我詳細道來:一般來說當合並兩個文件樹時,如果遇到沖突我們是需要手動去解決它的,但是目前我們要做的不是解決沖突,而是在引入 backend
歷史記錄的前提下完整保留 frontend
的內容。沖突肯定是會有的,即使兩個不同的項目也是如此,比方說兩邊都有 README.md
、app/
、config/
等文件或目錄,但是我們不關心沖突,我們只要保留frontend
的文件樹並且把 backend
的歷史記錄合並進來。
--strategy ours
會完成全部的合並解析,但是所有的沖突都以“我”為准,不允許外來的沖突覆蓋“我”的文件內容。最終的結果就是:
backend
的歷史記錄被合並到frontend
的歷史記錄中backend
的文件樹被讀取並和frontend
的文件樹比對進行沖突解析:- 如果發現沖突,以
frontend
為准,丟棄所有內容變更 - 沒沖突的則保留(但是我們也不要的,見后面的內容)
- 如果發現沖突,以
這也是后面緊接着使用 --no-commit
的原因,該選項會在合並解析完成后中斷,停留在最后的提交步驟之前。我們知道,只要你還沒commit,那么 merge 的結果就暫時保存在緩存區中,只有完成提交步驟合並才算徹底完成(文件樹被正式改變)。這就給我們一個機會來重新讀取 backend
的文件樹,並改寫其保存的位置。
第4
步創建目標子目錄(很重要!)。
第5
步開始 read-tree 了,--prefix
用於指定文件樹讀取后保存的路徑,相對於當前路徑並且一定要追加 /
。-u
是說在讀取后更新index,使得 working tree 與 index 保持同步。如果你不小心忘了加 -u
,可以在這一步之后執行 git add --update
,一樣的效果。
這一步在背后有些細節比較抽象,之前的 merge 也曾讀取過 backend
的文件樹,但經過沖突解析之后已經面目全非,分析如下:
- 有沖突的被丟棄,因此一部分文件/目錄其實已經不存在了
- 沒沖突的被保留,但是路徑還在
frontend
的根路徑下
經過再次 read-tree,上面的“遺跡”得以修復,結果如下:
- 有沖突的因為已被丟棄,所以直接從本次讀取中獲得,且路徑前面追加
--prefix
選項的值 - 沒沖突的雖然被保留,但是由於本次讀取追加了-- prefix,所以它們的路徑也被改變,相當於在緩存里做了一次
git mv。
好了,重點就是這些,之后的步驟都很尋常,只要小心操作就沒什么難理解的。