採用 GitSubtree 的方式來共用多個 project 會共用的 sourceCode,不過 subtree 的資料似乎不多,查到的資料都沒有順利的讓我完成我的 subtree 情境,所以自己參考資料並且嘗試成功後,就寫一篇 blog 來記錄一下,希望 subtree 以後會更方便用
寫在前面的前面 (2017/05/06更新)
經過了大概幾個月的使用,近日決定放棄用 Subtree 改回用 SubModule,因為遇到了一些問題,最嚴重的是兩個使用同一個 Subtree 會莫名的不同步,
不同步的意思就是一邊更新上去了,並確認在 Github 上已經是新的,另外一邊 subtree pull 回來的竟然不是最新的,這樣就算了,此時把 subtree push 回去,
要發 PR 還會發現竟然會把之前修改的蓋回去!!這也不是第一次遇到,第二次遇到,所以決定要放棄 Subtree 改回去 Submodule ,應該穩定運行是沒有問題,雖然麻煩了一點。
其他的問題例如:
1. 你在 Subtree 的 repo 會看到許多的 commit ,那些 commit 是使用 subtree repo 的人所造成的,並不一定真的對 subtree repo 裡面的檔案有影響
2. Main project 把修改 push 回 subtree repo 後,發 PR 竟然檔案沒有任何變更,這樣就算了,直接 clone subtree repo 下來,下 merge 竟然會說是 unrelated repo... 真的是 WTF
也許是我 git 指令及使用方式不熟悉,所以才會有此問題,但現階段來說研究這個浪費時間,所以決定改走回 submodule 的老路。
不過如果你還是有興趣試看看的話,歡迎繼續往下看
寫在前面
為什麼會用到 SubTree 呢?因為之前同事有用了 SubModule 覺得麻煩,雖然目前 Git 的 GUI 工具其實應該都有支援了,但如果是下指令的話,相較於 SubTree 來說是繁瑣了一些。
最近共用 Library Project 的需求再起(WPF & UWP)所以花了點時間找了替代 SubModule 的方案,看起來就只有 SubTree 囉~
看到一些地方也說建議大家可以用 SubTree 來替代 SubModule
SubTree vs SubModule
當初的出發點只是要找一個替代品,沒有仔細的想過這問題,也是被問到後才去查的,其實目的上當然是差不多,但使用的情境上,或者看指令的設計上還是有差距的
查到 這篇 覺得解釋的不錯,直接把原文節錄出來
- submodule is a better fit for component-based development, where your main project depends on a fixed version of another component (repo). You keep only references in your parent repo (gitlinks, special entries in the index)
- subtree is more like a system-based development, where your all repo contains everything at once, and you can modify any part.
我是這麼理解的
- SubModule 適合像是說所謂的共用函示庫你是沒有主空權的狀況下,通常你用的都是一個固定版本,就是變動不那麼頻繁的函示庫,在指令的使用情境上 SubModuel 的確有比較繁瑣
- SubTree 就是比較像我們需要的情境,會跟著 Main Project 一起成長的與改變的,在指令上使用相對簡單許多
關於 git subtree vs git submodule 指令的操作,可以看 這篇 ,在裡面就可以看到使用 git subModule 的狀況下,指令需要滿繁瑣的,而 subTree 就幾個簡單的指令就搞定了
But... 人生最想不到的就是這個But...不然我也不會想要寫這一篇 blog 了 XD
就讓我來說一下從 SubTree 從頭開始和我遇到的狀況吧~(指令詳細的參數使用方式就麻煩自行去查,謝謝~)
一、分割原來的原始碼
因為我們要共用的函示庫本來是我們 main repo 的一個資料夾,所以第一個步驟就是要把這個資料夾切割出來成為另外一個 repo,而 git 就有提供了一個 git subtree split 的指令,就可以指定一個目錄,把那個目錄的東西切割到另外一個 branch 去,
接著就可以把那個 branch 的東西 push 到你遠端的另外一個函示庫專用的 repo 了。
然後就遇到第一個問題,在 split 後我看 split 出來的資料夾也沒有任何東西 (ex: .git 的資料夾,沒有 .git 資料夾就不會有原來的 commit log 了) 阿我這樣到底要怎麼 push 啦?XD 從 GUI tool 是可以看到我 split 出來的另外一個 branch沒錯,結果搞半天後,
發現因為那個是個 branch 所以索性就切換到那個 branch,然後就可以 push 了 囧
OK~ 解決
二、加入遠端的 repo 為 subtree 並且 push 回去 main repo 後讓 git subtree 指令也都運作正常
這邊就是卡最久的地方,出乎意料的麻煩,不過也多學到了一些東西就是。
要加入一個另外一個 repo 當作自己的 Subtree 來源,首先當然就是要用 git remote add 來增加一個 remote 的來源,就讓新增的這個 remote 叫 shareLibraryRepo 吧!
然後就是要把它加入自己的 main repo 當作 subtree 啦~ 這時候我用第一時間查到的指令就是用 git subtree add -P ShareLibrary shareLibraryRepo master 來把 repo 放到我的 ShareLibrary 的資料夾當作 subtree ,
一切都看起來很簡單順利,然後我就把 main repo 給 push 回去,這時候當然要自己測試一下這樣測試一下別人拉回去後,如果要用 git subtree pull 可不可以正常運作囉!
所以我就弄了一個乾淨的 main repo ,一樣加了一個叫 shareLibraryRepo remote 後,下了 git subtree pull -P ShareLibrary shareLibraryRepo master 的指令,要來更新看看,結果就遇到了
的錯誤訊息... WTF... 那時候以為中間那邊弄錯了,重試了一次結果還是遇到一樣的問題... 囧
只好再去找資料,又花了點時間結果找到 Github 其實就有 這篇 在介紹 subtree merge 的方式,照著上面的步驟做後,成功了~ 用了乾淨的 main repo 後,可以正常的下 git subtree pull 的指令更新 shareLibraryRepo 的 repo 了!
不過... 事情當然沒有這麼簡單... =.=
當我開心的在 main repo 做了一些修改,包含有修改到 ShareLibrary 的東西,所以 commit 完 main repo 後,想要把對於 ShareLibrary 修改的東西一樣 push 回去,然後就開心的下了 git subtree push -P ShareLibrary shareLibrary master,然後就遇到
error: failed to push some refs to 'git@github.com:***'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. Merge the remote changes (e.g. 'git pull')
hint: before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.
的錯誤訊息... 被 reject !!! WTF... 然後又查半天找不到什麼好方式,然後我就轉方式想說一不做二不休,想說用 force push 的方式,但很不幸的 git subtree push 並不支援 --force 的參數,那....有辦法嗎?答案是有的!請參考下列指令的方式
利用的是 git push 的 chain command 的方式來達到,嗯~總算鬆了一口起,起碼剛剛改的一堆東西還可以回去 shareLibrary 的 repo!
但是這不是個解決問題的方式,總不能每次都要 force push 啊!!所以又花了時間嘗試了整合 subtree 的指令步驟,最後終於發現了關鍵的一個地方...直接講結論...
1. 用 git subtree add 是對的!
2. 記得下完 git subtree add 後,一定要多下一個下面的指令!記得下完 git subtree add 後,一定要多下一個下面的指令!記得下完 git subtree add 後,一定要多下一個下面的指令!因為很重要所以說三次
這行指令就是建立好 subtree 關係的關鍵步驟啊!!!(抱頭)就差了這一個步驟,所以讓我一開始會遇到 refusing to merge unrelated histories 的錯誤訊息 >"<
真的是雷!我其實不確定這到底是 bug 還是 feature (攤手)
總之在這之後,git subtree pull/push 都運作正常啦!(灑花)
王子和公主從此過著幸福快樂的日子
寫在後面
嘗試了 SubTree 的東西,大概有點心得
- main repo 有沒有加入 subtree 對 main repo 的操作沒有任何影響,就把它視為 main repo 的一步份即可,無須特別理會
- 換句話說修改到 subtree 裡面的 sourcecode commit & push 回去 main repo 後,其他在同一個 main repo 的人不需要用 subtree 指令去 pull,就直接 pull main repo 即可,因為基本上它就是 main repo 的一部分
- 有影響的狀況只有
- (1) subtree 的 repo 有更新,並且你想要 pull 回來的時候
- (2) 你有修改到 subtree 的東西並且想 push 回去的時候
- 要 pull / push subtree 的 repo 幾個步驟
- (1) 加入 shareLibrary 的 repo 為 remote 的來源(這個只要加過一次即可)
- (2) 使用 git subtree 的指令來 pull / pull
- git subtree 僅有 add / pull / push / split / merge 幾個 command 而已,其實沒有太複雜
- split 比較特別,用途是將你目前 repo 的一個目錄可以切成另外一個 repo,目的就是切割你原來 repo 的一個目錄然後可以當作 subtree
- subtree 最好不要加到與原來 shareLibrary 相同的路徑,不然原來的 commit 歷史紀錄會被一併推回去遠端的 repo (這點我不確定是不是因為這樣的緣故,不過我是這樣解決的)
- 建議可以看看的連結:
- mastering subtree
- 裡面有提到 git subtree 的指令,其實是包裝好的一些指令的只是先幫你包裝好而已
- 例如 git subtree pull/push 其實就跟上面看到的 force push 的是一樣的,其實都是透過 chaine command 的方式做到,都會幫你串 git subtree split 的指令
- 例如 git subtree add 也是幫你做好了在那篇 Github 文章的幾個步驟
- mastering subtree
最後也是要來說一下目前用 subtree 所覺得的缺點
- git subtree push 會花很久的時間,就像心得裡面因為他會先 split 然後再 push,split 的時候就會計算那個資料夾的 commit 哪些是有關係的,通常都會花一段時間,目前這個還沒找到解法
- 所以如果要比較快的 workaround 的方式,就是直接去修改 shareLibrary 的那個 repo,然後再用 git subtree pull 的方式拉回來,不過這樣其實應該比較麻煩,所以就不要每次修改到 subtree 就都要 push 就好
- GUI 工具支援度不足,目前只知道 SourceTree 有支援,不過還好,就下下指令而已
以上~ 感謝各位的觀賞~ 我們下次再見~
2017/01/18 更新:
上面有說道 git subtree push 會花很久的時間,後來發現原來是有一個地方做的方式改變一下就不會了,如果你原本就照上面的方式使用 git subtree add 的指令,沒有沒有加其他的參數的話,應該就不會遇到我說的 git subtree push 的時候會很久的問題;
我會說會花很久的原因是因為我再下 git subtree add 的時候多加了一個 --squash 的參數,這個參數可以讓 subtree 加入的時候只會產生一個 commit ,但是看樣子這樣的缺點就是因為他沒有完整的 subtree 的 history,所以會導致在 subtree push 的時候必須"重新"
檢視一次紀錄所導致,所以... 如果沒有特別理由,還是不要用 --squash 嚕~