使用分支

在這一點上,你必須理解每一次提交是怎樣建立整個新的文件系統樹(叫做「修訂版本」)的,如果沒有,可以回頭去讀「修訂版本」一節

對於本章節,我們會回到第 1 章 基本概念的同一個例子,還記得你和你的合作者Sally分享一個包含兩個項目的版本庫,paintcalc。注意圖 4.2 「開始規劃版本庫」,然而,現在每個項目的都有一個trunkbranches子目錄,它們存在的理由很快就會清晰起來。

圖 4.2. 開始規劃版本庫

開始規劃版本庫

像以前一樣,假定Sally和你都有「calc」項目的一份拷貝,更準確地說,你有一份/calc/trunk的工作副本,這個項目的所有的文件在這個子目錄裡,而不是在/calc下,因為你的小組決定使用/calc/trunk作為開發使用的「主線」。

假定你有一個任務,將要對項目做基本的重新組織,這需要花費大量時間來完成,會影響項目的所有文件,問題是你不會希望打擾Sally,她正在處理這樣或那樣的程序小Bug,一直使用整個項目(/calc/trunk)的最新版本,如果你一點一點的提交你的修改,你一定會干擾Sally的工作。

一種策略是自己閉門造車:你和Sally可以停止一個到兩個星期的共享,也就是說,開始作出本質上的修改和重新組織工作副本的文件,但是在完成這個任務之前不做提交和更新。這樣會有很多問題,首先,這樣並不安全,許多人習慣頻繁的保存修改到版本庫,工作副本一定有許多意外的修改。第二,這樣並不靈活,如果你的工作在不同的計算機(或許你在不同的機器有兩份/calc/trunk的工作副本),你需要手工的來回拷貝修改,或者只在一個計算機上工作,這時很難做到共享你即時的修改,一項軟體開發的「最佳實踐」就是允許審核你做過的工作,如果沒有人看到你的提交,你失去了潛在的反饋。最後,當你完成了公司主幹代碼的修改工作,你會發現合併你的工作副本和公司的主幹代碼會是一件非常困難的事情,Sally(或者其他人)也許已經對版本庫做了許多修改,已經很難和你的工作副本結合—當你單獨工作幾周後運行svn update時就會發現這一點。

最佳方案是建立你自己的分支,或者是版本庫的開發線。這允許你保存破壞了一半的工作而不打擾別人,儘管你仍可以選擇性的同你的合作者分享訊息,你將會看到這是怎樣工作的。

建立分支

建立分支非常的簡單—使用svn copy命令給你的工程做個拷貝,Subversion不僅可以拷貝單個文件,也可以拷貝整個目錄,在目前情況下,你希望作/calc/trunk的拷貝,新的拷貝應該在哪裡?在你希望的任何地方—它只是在於項目的政策,我們假設你們項目的政策是在/calc/branches建立分支,並且你希望把你的分支叫做my-calc-branch,你希望建立一個新的目錄/calc/branches/my-calc-branch,作為/calc/trunk的拷貝開始它的生命週期。

有兩個方法作拷貝,我們先介紹一個混亂的方法,只是讓概念更清楚,首先取出一個項目的根目錄,/calc

$ svn checkout http://svn.example.com/repos/calc bigwc
A  bigwc/trunk/
A  bigwc/trunk/Makefile
A  bigwc/trunk/integer.c
A  bigwc/trunk/button.c
A  bigwc/branches/
Checked out revision 340.

建立一個備份只是傳遞兩個目錄參數到svn copy命令:

$ cd bigwc
$ svn copy trunk branches/my-calc-branch
$ svn status
A  +   branches/my-calc-branch

在這個情況下,svn copy命令迭代的將trunk工作目錄拷貝到一個新的目錄branhes/my-calc-branch,像你從svn status看到的,新的目錄是準備新增到版本庫的,但是也要注意A後面的「+」號,這表明這個準備新增的東西是一份備份,而不是新的東西。當你提交修改,Subversion會通過拷貝/calc/trunk建立/calc/branches/my-calc-branch目錄,而不是通過網路傳遞所有資料:

$ svn commit -m "Creating a private branch of /calc/trunk."
Adding         branches/my-calc-branch
Committed revision 341.

現在,我們必須告訴你建立分支最簡單的方法:svn copy可以直接對兩個URL操作。

$ svn copy http://svn.example.com/repos/calc/trunk \
           http://svn.example.com/repos/calc/branches/my-calc-branch \
      -m "Creating a private branch of /calc/trunk."

Committed revision 341.

從版本庫的視點來看,其實這兩種方法沒有什麼區別,兩個過程都在版本341建立了一個新目錄作為/calc/trunk的一個備份,這些可以在圖 4.3 「版本庫與複製」看到,注意第二種方法,只是執行了一個立即提交。 [20]這是一個簡單的過程,因為你不需要取出版本庫一個龐大的鏡像,事實上,這個技術不需要你有工作副本,這是大多數用戶建立分支的方式。

圖 4.3. 版本庫與複製

版本庫與複製

在分支上工作

現在你已經在項目裡建立分支了,你可以取出一個新的工作副本來開始使用:

$ svn checkout http://svn.example.com/repos/calc/branches/my-calc-branch
A  my-calc-branch/Makefile
A  my-calc-branch/integer.c
A  my-calc-branch/button.c
Checked out revision 341.

這一份工作副本沒有什麼特別的,它只是版本庫另一個目錄的一個鏡像罷了,當你提交修改時,Sally在更新時不會看到改變,她是/calc/trunk的工作副本。(確定要讀本章後面的「使用分支」一節svn switch命令是建立分支工作副本的另一個選擇。)

我們假定本周就要過去了,如下的提交發生:

  • 你修改了/calc/branches/my-calc-branch/button.c,生成修訂版本342。

  • 你修改了/calc/branches/my-calc-branch/integer.c,生成修訂版本343。

  • Sally修改了/calc/trunk/integer.c,生成了修訂版本344。

現在有兩個獨立開發線,圖 4.4 「一個文件的分支歷史」顯示了integer.c的歷史。

圖 4.4. 一個文件的分支歷史

一個文件的分支歷史

當你看到integer.c的改變時,你會發現很有趣:

$ pwd
/home/user/my-calc-branch

$ svn log -v integer.c
------------------------------------------------------------------------
r343 | user | 2002-11-07 15:27:56 -0600 (Thu, 07 Nov 2002) | 2 lines
Changed paths:
   M /calc/branches/my-calc-branch/integer.c

* integer.c:  frozzled the wazjub.

------------------------------------------------------------------------
r341 | user | 2002-11-03 15:27:56 -0600 (Thu, 07 Nov 2002) | 2 lines
Changed paths:
   A /calc/branches/my-calc-branch (from /calc/trunk:340)

Creating a private branch of /calc/trunk.

------------------------------------------------------------------------
r303 | sally | 2002-10-29 21:14:35 -0600 (Tue, 29 Oct 2002) | 2 lines
Changed paths:
   M /calc/trunk/integer.c

* integer.c:  changed a docstring.

------------------------------------------------------------------------
r98 | sally | 2002-02-22 15:35:29 -0600 (Fri, 22 Feb 2002) | 2 lines
Changed paths:
   M /calc/trunk/integer.c

* integer.c:  adding this file to the project.

------------------------------------------------------------------------

注意,Subversion追蹤分支上的integer.c的歷史,包括所有的操作,甚至追蹤到拷貝之前。這表示了建立分支也是歷史中的一次事件,因為在拷貝整個/calc/trunk/時已經拷貝了一份integer.c。現在看Sally在她的工作副本運行同樣的命令:

$ pwd
/home/sally/calc

$ svn log -v integer.c
------------------------------------------------------------------------
r344 | sally | 2002-11-07 15:27:56 -0600 (Thu, 07 Nov 2002) | 2 lines
Changed paths:
   M /calc/trunk/integer.c

* integer.c:  fix a bunch of spelling errors.

------------------------------------------------------------------------
r303 | sally | 2002-10-29 21:14:35 -0600 (Tue, 29 Oct 2002) | 2 lines
Changed paths:
   M /calc/trunk/integer.c

* integer.c:  changed a docstring.

------------------------------------------------------------------------
r98 | sally | 2002-02-22 15:35:29 -0600 (Fri, 22 Feb 2002) | 2 lines
Changed paths:
   M /calc/trunk/integer.c

* integer.c:  adding this file to the project.

------------------------------------------------------------------------

sally看到她自己的344修訂,你做的343修改她看不到,從Subversion看來,兩次提交只是影響版本庫中不同位置上的兩個文件。然而,Subversion顯示了兩個文件有共同的歷史,在分支拷貝之前,他們使用同一個文件,所以你和Sally都看到版本號303到98的修改。

分支背後的關鍵概念

在本節,你需要記住兩件重要的課程。首先,Subversion並沒有內在的分支概念—只有拷貝,當你拷貝一個目錄,這個結果目錄就是一個「分支」,只是因為你給了它這樣一個含義而已。你可以換一種角度考慮,或者特別對待,但是對於Subversion它只是一個普通的拷貝,只不過碰巧包含了一些額外的歷史訊息。第二,因為拷貝機制,Subversion的分支是以普通文件系統目錄存在的,這與其他版本控制系統不同,它們都為分支定義了另一維度的「標籤」。



[20] Subversion不支持跨版本庫的拷貝,當使用svn copy或者svn move直接操作URL時你只能在同一個版本庫內操作。