2012-01-20 23:18:24

我的 git-svn 用法

去年在前公司的 wiki 上寫過一次我自己如何使用 git 管理 svn repo, 今年在本公司的 wiki 也寫了一點, 今天又被人問如何使用, 所以我決定來稍微寫一下我自己的 git-svn 用法, 以後只要丟 link 給別人就好了. XD

概論

先簡單介紹一下 git 和 svn 的差異之處. 我認為比較需要注意的部份如下:

  1. git 是分散式版本控制系統. 這點和 svn 的中央控管式有很大的不同. 最顯著的差別有:
    • git 的 commit 只是送到本地的 repo 而已. 如果本地 repo 有上游的話, 需要手動送回上游, 才會把本地更動送回去. 也就是說, 在本地 repo 隨意亂搞是不會影響上游的.
    • 因為是分散式, 所以本地 repo 擁有所有的 history.
    • 因為本地 repo 擁有所有的 history, 所以所有的動作都在本地進行, 速度飛快~
    • 除了跟上游 repo 同步之外, 其它時間不會使用到網路. 意思是你在車上, 在飛機上, 在深山的溫泉裡都可以進行 commit, 等到有網路時再同步.
  2. git 分支 (branch) 功能超容易, merge 的功能也超強. svn 以目錄習慣來做分支. 雖然比 cvs 方便, 但是也因此被很多人誤用. 我在工作上看過好幾次別人跨越分支目錄在 commit 程式的.

初步設定

在開始使用之前, 系統要先裝 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 repo 的分支

很不幸地, 我們不能完全丟掉 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 的分支功能非常強大, 使用 git 而不用到它的分支功能, 可以說是一種浪費. XD

本節會放在 svn branch 的後面, 是因為我會介紹我慣用的命名法. 它可以讓我只要看分支的名稱, 就可以知道它的來源, 以決定我在分支做完之後要 merge 回哪裡, 減少我犯錯的機會.

命名法很簡單, 它包含三個部份:

{SOURCE_TYPE}-{NAME}-{SOURCE}

其中:

  • {SOURCE_TYPE}: gitsvn. 標明它的上游在哪一個 repo.
  • {NAME}: 分支名稱. 基本上就是描述這個分支要做什麼.
  • {SOURCE}: 從哪一個分支建立出來的.

git 分支的建立原則上是:

  • svn-* 分支與上游 svn 的分支為一對一的對應關係.
  • git-*-* 應該是由本地的 svn-* 再建立出來.

來看幾個例子會比較清楚:

  • svn-BRANCH: 這是一個對應到上游 svn 的 BRANCH 分支的分支.
  • git-GreatIdea-trunk: 這是一個從本地 git trunk 分支建立出來, 名叫 GreatIdea 的分支. 它對應到的 svn 分支為 trunk. 還記得 master 永遠對應到 trunk?
  • git-Refactor-BRANCH: 這是從本地的 svn-BRANCH 分支建立出來, 名叫 Refactor 的分支.

所以 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)

由 plasma 於 11:18 PM 所發表 | 迴響 (331)