去年在前公司的 wiki 上寫過一次我自己如何使用 git 管理 svn repo, 今年在本公司的 wiki 也寫了一點, 今天又被人問如何使用, 所以我決定來稍微寫一下我自己的 git-svn 用法, 以後只要丟 link 給別人就好了. XD
先簡單介紹一下 git 和 svn 的差異之處. 我認為比較需要注意的部份如下:
在開始使用之前, 系統要先裝 git 和 git-svn. 這部份我相信稍微搜尋一下, 應該就有一卡車的文章. 而且各 un*x 系統應該都有提供套件可供安裝, 應該不會太困難.
程式裝好之後, 再來就是先把自己想要使用的 svn repo clone 一份下來. 如果 svn repo 使用 svnbook 建議的目錄結構, 它應該會有 SVN_URL/{trunk,branches,tags} 的結構. 指令就會是:
git svn init -s SVN_URL {TARGET_DIR}
其中 {TARGET_DIR} 是想要儲存在本地磁碟的目錄名稱. 如果你的狀況複雜點, 請參考 git svn --help 看看有什麼參數可以使用.
請注意以下有關於分支的介紹, 都是以標準目錄結構的 svn repo 來講的. 如果你的 svn repo 不是的話 (是說真的有人這樣做嗎?), 那你得自己再研究怎麼用 git 管理 svn repo.
這個步驟會把 svn repo 整個 clone 下來. 視 svn repo 的大小, 這個步驟可能只要幾分鐘, 也可能要幾個禮拜 (是的, 我跑過超過一個禮拜的). 完成之後, 你應該就有一個上游是 svn repo 的 git repo 可以用了. 可以用
git svn info
看看這個 git repo 對應的 svn repo 資訊.
現在可以用
git branch
看看目前的 git 分支. 應該只會有 master. 這個是對應到 svn repo 的 trunk. 原則上我會把 master 只對應到 svn 的 trunk. 我以前曾經讓它在不同的 svn 分支切換, 結果有一次就造成嚴重的問題, 一堆 commit 都不見了. 修了很久才把消失的 commit 都撿回來. 以下的使用方法, 在我自己使用一年多以來, 還沒發生過問題. 只要好好遵守, 我想應該不會出什麼大問題才是.
懶得一步步寫, 直接看吧.
git svn rebase # 抓回上游 svn repo 的更新 git log --name-status # 類似 svn log -v. 看看 commit log. # 編輯程式 git diff # 看看改了什麼 git add -i # 挑要 commit 的檔案. -i 超方便的~ git commit # commit 回 *本地* 的 git repo # 重複 編輯程式 到 commit 之間的動作 git svn rebase # 再跟上游 svn repo 進行同步. 有 conflict 要修好~ git svn dcommit # 把本地的 commit 送回 svn repo
需要一提的是 git 在 repo 和 working copy 之間, 還有一個 stage. 你得把 working copy 裡需要 commit 的檔案, 先透過 git add 加進 stage, 再用 git commit 將 stage 裡的更動 commit 回 git repo. 我會建議使用 git add -i 提供的互動命令列來把檔案加進 stage, 還滿方便的.
很不幸地, 我們不能完全丟掉 svn. 要建立 svn 的分支, 還是建議使用 svn 來建. 雖然有 git svn branch 可以使用, 不過我並不推薦.
另外, 要 merge svn 的分支一樣得使用 svn. git 在這裡也幫不上忙. 最主要的原因, 是 git-svn 並不會維護 svn:merge-info. 這在 svn 裡可是很重要的!
但是建立和 merge svn 的分支的情況畢竟不多. 絕大多數的使用情況, 還是基本流程而已. 我們還是可以用 git 來同步上游 svn 分支.
最開始的 git clone -s 會把分支和標記 (tag) 都一併複製下來. 我們可以使用
git branch -r
來看看上游 svn 的分支. 不過它會列出 *所有* 曾經使用過的分支名稱, 所以我們還是得使用 svn ls SVN_URL/branches 來看看目前有哪些分支.
決定好要使用哪個 svn repo, 接下來就是切換過去使用. 假設分支的名稱叫 BRANCH:
git checkout -b svn-BRANCH remotes/BRANCH
現在我們建立了一個本地的 git 分支, 它的名稱是 svn-BRANCH, 它的上游是 svn 的 BRANCH 分支, 而且我們也切換到 svn-BRANCH 分支裡來了. 接下來的基本流程, 就都是發生在 svn-BRANCH 裡. 和上游同步也會送到 BRANCH 分支去:
git svn fetch --all # 抓回上游 svn repo *所有* 的更新 git svn rebase # 同步上游 svn repo 的更新 git log --name-status # 接近 svn log -v. 看看 commit log. # 編輯程式 git diff # 看看改了什麼 git add -i # 挑要 commit 的檔案 git commit # commit 回 *本地* 的 git repo # 重複 編輯程式 到 commit 之間的動作 git svn rebase # 再跟上游 svn repo 進行同步. 有 conflict 要修好~ git svn dcommit # 把本地的 commit 送回 svn repo
請注意這裡多了 git svn fetch --all. 這裡是抓回上游 svn repo 的所有更新, 包含各個分支的更新. 而且它只會抓到本地端而已, 不會合併至目前的 git repo. 要到 git svn rebase 指令才會合併至目前的 git repo. 使用 git svn fetch --all 的原因是分支之間可能會有 merge, git-svn 會自動找出來, 並建立之間的關聯.
前面有提到 git 的分支功能非常強大, 使用 git 而不用到它的分支功能, 可以說是一種浪費. XD
本節會放在 svn branch 的後面, 是因為我會介紹我慣用的命名法. 它可以讓我只要看分支的名稱, 就可以知道它的來源, 以決定我在分支做完之後要 merge 回哪裡, 減少我犯錯的機會.
命名法很簡單, 它包含三個部份:
{SOURCE_TYPE}-{NAME}-{SOURCE}
其中:
git 分支的建立原則上是:
來看幾個例子會比較清楚:
所以 git 分支的流程大概是:
git checkout svn-BRANCH # 假設我們目前在 svn-BRANCH git checkout -b git-Refactor-BRANCH # 依普拉拉命名法建立 git 分支 # 編輯程式 git diff # 看看改了什麼 git add -i # 挑要 commit 的檔案 git commit # commit 回 *本地* 的 git repo # 重複 編輯程式 到 commit 之間的動作 git checkout svn-BRANCH # 準備 merge 回 svn-BRANCH git merge git-Refactor-BRANCH # merge git log --name-status # 檢查 merge 是否有問題 git branch -D git-Refactor-BRANCH # 移除剛剛建立的分支 # 接下來同 "svn repo 的分支"
git merge
可能會產生衝突. 解決之後, 記得要使用 git add
, git commit
手動 commit 進去.
使用 git branch -D
強制移除分支, 是因為前一步的 git merge
只會 merge commit, 但是還不會標記 merge point. 此時移除分支, 會有 "分支未 merge" 的警告出現. 事實上我們也不要 git 標記 merge point, 否則送回 svn repo 會變成一個什麼更動都沒有的 commit, 反而更困擾.
以上差不多就是我日常使用的 git-svn 命令. 其它像是 git stash
也是非常方便的命令, 這裡就不寫. (其實是因為我想睡了... v.v)