原本已經不寫文字博客了,通常心得都錄成了視頻(這在我看來是更好的方式),可是今天遇到一個關於 Git 的問題不太好重現也不便於錄製視頻,加上它自己很具備表明性也頗有用,因此仍是記錄於此。前端
一箇中型規模項目,開始規劃時就打算採用 C/S 架構,後端是單純的 API 服務,前端在 Web 上搞一個 SPA,以後再搞其餘端也就瓜熟蒂落了。只能夠第一次弄沒經驗,有些細節最初沒有考慮到。git
建立項目的時候先後端真是徹底分離的,分紅了兩個目錄,建立了兩個 repos。一開始只有一我的乾的時候倒也沒什麼,開兩個窗口切來切去也就罷了,後來一是部署起來麻煩,二來主要是其餘開發者加入後,代碼的版本管理、提交、合併、審覈等等等等都變得愈來愈繁瑣。後端
後來一想:架構上分離而已,幹嗎非要兩個目錄兩個 repos?真是自找麻煩!因而就開始考慮整合。緩存
把兩個目錄併成一個倒不難,可是要完整保留雙方的歷史記錄就有些麻煩了,這也是惟一一個必需要實現的目標。bash
首先爲了便於描述,約定整合前兩個目錄分別叫作 frontend
和 backend
,合併後的結構與名稱應當以下:架構
- project/ => 即最開始的 frontend,整合完後改名 - .gitignore => 合併兩個 repos 的忽略文件 - .git/ => 最終僅餘一個 repo + client/ => 對應 frontend + server/ => 對應 backend
如下步驟是以 frontend
爲基點,把 backend
移進來,實際上反過來也是同樣的,自行替換對應的名稱便可。在開始以前先清理兩個 repos 裏的工做記錄,該提交的提交,該備份的備份,保持乾淨。app
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 '遷移整合完成!'
以上是完整的步驟先列出來方便參考,下面作一個詳細的解釋。frontend
整個過程當中主要用到的工具是 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
選項的值git mv
好了,重點就是這些,以後的步驟都很尋常,只要當心操做就沒什麼難理解的。