如何使用CVS進行版本控制

GitHub 網站發佈於 2008 年。若是你的軟件工程師職業生涯跟我同樣,也是晚於此時間的話,Git 多是你用過的惟一版本控制軟件。雖然其陡峭的學習曲線和不直觀地用戶界面時常會遭人抱怨,但不能否認的是,Git 已經成爲學習版本控制的每一個人的選擇。cvs和Git並沒有差異,今天就來介紹Git的前身-cvs

Stack Overflow 2015 年進行的開發者調查顯示,69.3% 的被調查者在使用 Git,幾乎是排名第二的 Subversion 版本控制系統使用者數量的兩倍。2015 年以後,也許是由於 Git 太受歡迎了,你們對此話題再也不感興趣,因此 Stack Overflow 中止了關於開發人員使用的版本控制系統的問卷調查。
GitHub 的發佈時間距離 Git 自身發佈時間很近。2005 年,Linus Torvalds 發佈了 Git 的首個版本。如今的年經一代開發者可能很難想象「版本控制軟件」一詞所表明的世界並不只僅只有 Git,雖然這樣的世界誕生的時間並不長。除了 Git 外,還有不少可供選擇。那時,開源開發者較喜歡 Subversion,企業和視頻遊戲公司使用 Perforce (到現在有些仍在用),而 Linux 內核項目依賴於名爲 BitKeeper 的版本控制系統。
其中一些系統,特別是 BitKeeper,會讓年經一代的 Git 用戶感受很熟悉,上手也很快,但大多數相差很大。除了 BitKeeper,Git 以前的版本控制系統都是以不一樣的架構模型爲基礎運行的。《Version Control By Example》一書的做者 Eric Sink 在他的書中對版本控制進行了分類,按其說法,Git 屬於第三代版本控制系統,而大多數 Git 的前身,即流行於二十世紀九零年代和二十一世紀早期的系統,都屬於第二代版本控制系統。2 第三代版本控制系統是分佈式的,第二代是集中式。大家之前大概都聽過 Git 被描述爲一款「分佈式」版本控制系統。我一直都不明白分佈式/集中式之間的區別,隨後本身親自安裝了一款第二代的集中式版本控件系統,並作了相關實驗,至少明白了一些。
我安裝的版本系統是 CVS。CVS,即 「併發版本系統Concurrent Versions System」 的縮寫,是最初的第二代版本控制系統。大約十年間,它是最爲流行的版本控制系統,直到 2000 年被 Subversion 所取代。即使如此,Subversion 被認爲是 「更好的 CVS」,這更進一步突出了 CVS 在二十世紀九零年代的主導地位。html

CVS 最先是由一位名叫 Dick Grune 的荷蘭科學家在 1986 年開發的,當時有一個編譯器項目,他正在尋找一種能與其學生合做的方法。3 CVS 最初僅僅只是一個包裝了 RCS(修訂控制系統Revision Control System) 的 Shell 腳本集合,Grune 想改進這個第一代的版本控制系統。 RCS 是按悲觀鎖模式工做的,這意味着兩個程序員不能夠同時處理同一個文件。須要編輯一個文件話,首先得向 RCS 系統請求一個排它鎖,鎖定此文件直到完成編輯,若是你想編輯的文件有人正在編輯,你就必須等待。CVS 在 RCS 基礎上改進,並把悲觀鎖模型替換成樂觀鎖模型,迎來了第二代版本控制系統的時代。如今,程序員能夠同時編輯同一個文件、合併編輯部分,隨後解決合併衝突問題。(後來接管 CVS 項目的工程師 Brian Berliner 於 1990 年撰寫了一篇很是易讀的關於 CVS 創新的 論文。)linux

從這個意義上來說,CVS 與 Git 並沒有差別,由於 Git 也是運行於樂觀鎖模式的,但也僅僅只有此點類似。實際上,Linus Torvalds 開發 Git 時,他的一個指導原則是 WWCVSND,即 「CVS 不能作的What Would CVS Not Do」。每當他作決策時,他都會力爭選擇那些在 CVS 設計裏沒有使用的功能選項。4 因此即便 CVS 要早於 Git 十多年,但它對 Git 的影響是反面的。git

我很是喜歡折騰 CVS。我認爲要弄明白爲何 Git 的分佈式特性是對之前的版本控制系統的極大改善的話,除了折騰 CVS 外,沒有更好的辦法。所以,我邀請你跟我一塊兒來一段激動人心的旅程,並在接下來的十分鐘內瞭解下這個近十年來無人使用的軟件。(能夠看看文末「修正」部分)程序員

CVS 入門後端

CVS 的安裝教程能夠在其 項目主頁 上找到。MacOS 系統的話,可使用 Homebrew 安裝。安全

因爲 CVS 是集中式的,因此它有客戶端和服務端之區分,這種模式 Git 是沒有的。兩端分別有不一樣的可執行文件,其區別不太明顯。但要開始使用 CVS 的話,即便只在你的本地機器上使用,也必須設置 CVS 的服務後端。網絡

CVS 的後端,即全部代碼的中央存儲區,被叫作存儲庫 repository。在 Git 中每個項目都有一個存儲庫,而 CVS 中一個存儲庫就包含全部的項目。儘管有辦法保證一次只能訪問一個項目,但一箇中央存儲庫包含全部東西是改變不了的。架構

要在本地建立存儲庫的話,請運行 init 命令。你能夠像以下所示在家目錄建立,也能夠在你本地的任何地方建立。併發

$ cvs -d ~/sandbox init

CVS 容許你將選項傳遞給 cvscvs 命令自己或 init 子命令。出如今cvs 命令以後的選項默認是全局的,而出如今子命令以後的是子命令特有選項。上面所示例子中,-d 標誌是全局選項。在這兒是告訴 CVS 咱們想要建立存儲庫路徑在哪裏,但通常-d 標誌指的是咱們想要使用的且已經存在的存儲庫位置。一直使用 -d 標誌很單調乏味,因此能夠設置 CVSROOT 環境變量來代替。分佈式

由於咱們只是在本地操做,因此僅僅使用 -d 參考來傳遞路徑就能夠,但也能夠包含個主機名。

此命令在你的家目錄建立了一個名叫 sandbox 的目錄。 若是你列出sandbox 內容,會發現下面包含有名爲 CVSROOT 的目錄。請不要把此目錄與咱們的環境變量混淆,它保存存儲庫的管理文件。

恭喜! 你剛剛建立了第一個 CVS 存儲庫。

檢入代碼

假設你決定留存下本身喜歡的顏色清單。由於你是一個有藝術傾向但很健忘的人,因此你鍵入顏色列表清單,並保存到一個叫 favorites.txt 的文件中:

blue
orange
green
definitely not yellow

咱們也假設你把文件保存到一個叫colors 的目錄中。如今你想要把喜歡的顏色列表清單置於版本控制之下,由於從如今起的五十年間你會回顧下,隨着時間的推移本身的品味怎麼變化,這件事頗有意思。

爲此,你必須將你的目錄導入爲新的 CVS 項目。可使用import 命令:

$ cvs -d ~/sandbox import -m "" colors colors initial
N colors/favorites.txt
No conflicts created by this import

這裏咱們再次使用-d 標誌來指定存儲庫的位置,其他的參數是傳輸給import 子命令的。必需要提供一條消息,但這兒不必,因此留空。下一個參數colors ,指定了存儲庫中新目錄的名字,這兒給的名字跟檢入的目錄名稱一致。最後的兩個參數分別指定了 「vendor」 標籤和 「release」 標籤。咱們稍後就會談論標籤。
咱們剛將 colors 項目拉入 CVS 存儲庫。將代碼引入 CVS 有不少種不一樣的方法,但這是 《Pragmatic Version Control Using CVS》 一書所推薦方法,這是一本關於 CVS 的程序員實用指導書籍。使用這種方法有點尷尬的就是你得從新檢出check out工做項目,即便已經存在有 colors 此項目了。不要使用該目錄,首先刪除它,而後從 CVS 中檢出剛纔的版本,以下示:

$ cvs -d ~/sandbox co colors
cvs checkout: Updating colors
U colors/favorites.txt

這個過程會建立一個新的目錄,也叫作 colors。此目錄裏會發現你的源文件 favorites.txt,還有一個叫 CVS 的目錄。這個 CVS 目錄基本上與每一個 Git 存儲庫的.git 目錄等價。

作出改動

準備旅行。

和 Git 同樣,CVS 也有 status 命令:

$ cvs status
cvs status: Examining .
===================================================================
File: favorites.txt     Status: Up-to-date
   Working revision:    1.1.1.1 2018-07-06 19:27:54 -0400
   Repository revision: 1.1.1.1 /Users/sinclairtarget/sandbox/colors/favorites.txt,v
   Commit Identifier:   fD7GYxt035GNg8JA
   Sticky Tag:      (none)
   Sticky Date:     (none)
   Sticky Options:  (none)

到這兒事情開始陌生起來了。CVS 沒有提交對象這一律念。如上示,有一個叫 「提交標識符Commit Identifier」 的東西,但這多是一個較新版本的標識,在 2003 年出版的《Pragmatic Version Control Using CVS》一書中並無提到 「提交標識符」 這個概念。 (CVS 的最新版本於 2008 年發佈的。5 )

在 Git 中,咱們所談論某文件版本實際上是在談論如 commit 45de392 相關的東西,而 CVS 中文件是獨立版本化的。文件的第一個版本爲 1.1 版本,下一個是 1.2 版本,依此類推。涉及分支時,會在後面添加擴展數字。所以你會看到如上所示的 1.1.1.1 的內容,這就是示例的版本號,即便咱們沒有建立分支,彷佛默認的會給加上。

一個項目中會有不少的文件和不少次的提交,若是你運行 cvs log 命令(等同於 git log),會看到每一個文件提交歷史信息。同一個項目中,有可能一個文件處於 1.2 版本,一個文件處於 1.14 版本。

繼續,咱們對 1.1 版本的favorites.txt 文件作些修改(LCTT 譯註:原文此處示例有誤):

blue
orange
green
cyan
definitely not yellow

修改完成,就能夠運行cvs diff 來看看 CVS 發生了什麼:

$ cvs diff
cvs diff: Diffing .
Index: favorites.txt
===================================================================
RCS file: /Users/sinclairtarget/sandbox/colors/favorites.txt,v
retrieving revision 1.1.1.1
diff -r1.1.1.1 favorites.txt
3a4
> cyan

CVS 識別出咱們我在文件中添加了一個包含顏色 「cyan」 的新行。(實際上,它說咱們已經對 「RCS」 文件進行了更改;你能夠看到,CVS 底層使用的仍是 RCS。) 此差別指的是當前工做目錄中的favorites.txt 副本與存儲庫中 1.1.1.1 版本的文件之間的差別。

爲了更新存儲庫中的版本,咱們必須提交更改。Git 中,這個操做要好幾個步驟。首先,暫存此修改,使其在索引中出現,而後提交此修改,最後,爲了使此修改讓其餘人可見,咱們必須把此提交推送到源存儲庫中。

而 CVS 中,只須要運行 cvs commit 命令就搞定一切。CVS 會聚集它所找到的變化,而後把它們放到存儲庫中:

$ cvs commit -m "Add cyan to favorites."
cvs commit: Examining .
/Users/sinclairtarget/sandbox/colors/favorites.txt,v < -- favorites.txt
new revision: 1.2; previous revision: 1.1

我已經習慣了 Git,因此這種操做會讓我感到十分恐懼。由於沒有變動暫存區的機制,工做目錄下任何你動過的東西都會一股腦給提交到公共存儲庫中。你有過由於不爽,私下裏重寫了某個同事不佳的函數實現,但僅僅只是自我宣泄一下並不想讓他知道的時候嗎?若是不當心提交上去了,就太糟糕了,他會認爲你是個混蛋。在推送它們以前,你也不能對提交進行編輯,由於提交就是推送。仍是你願意花費 40 分鐘的時間來反覆運行 git rebase -i 命令,以使得本地提交歷史記錄跟數學證實同樣清晰嚴謹?很遺憾,CVS 裏不支持,結果就是,你們都會看到你沒有先寫測試用例。

不過,到如今我終於理解了爲何那麼多人都以爲 Git 不必搞那麼複雜。對那些早已經習慣直接 cvs commit 的人來講,進行暫存變動和推送變動操做確實是毫無心義的差事。

人們常談論 Git 是一個 「分佈式」 系統,其中分佈式與非分佈式的主要區別爲:在 CVS 中,沒法進行本地提交。提交操做就是向中央存儲庫提交代碼,因此沒有網絡鏈接,就沒法執行操做,你本地的那些只是你的工做目錄而已;在 Git 中,會有一個完徹底全的本地存儲庫,因此即便斷網了也能夠無間斷執行提交操做。你還能夠編輯那些提交、回退、分支,並選擇你所要的東西,沒有任何人會知道他們必須知道的以外的東西。

由於提交是個大事,因此 CVS 用戶不多作提交。提交會包含不少的內容修改,就像現在咱們能在一個含有十次提交的拉取請求中看到的同樣多。特別是在提交觸發了 CI 構建和自動測試程序時如此。

如今咱們運行cvs status ,會看到產生了文件的新版本:

$ cvs status
cvs status: Examining .
===================================================================
File: favorites.txt     Status: Up-to-date
   Working revision:    1.2 2018-07-06 21:18:59 -0400
   Repository revision: 1.2 /Users/sinclairtarget/sandbox/colors/favorites.txt,v
   Commit Identifier:   pQx5ooyNk90wW8JA
   Sticky Tag:      (none)
   Sticky Date:     (none)
   Sticky Options:  (none)

合併

如上所述,在 CVS 中,你能夠同時編輯其餘人正在編輯的文件。這是 CVS 對 RCS 的重大改進。當須要將更改的部分從新組合在一塊兒時會發生什麼?

假設你邀請了一些朋友來將他們喜歡的顏色添加到你的列表中。在他們添加的時候,你肯定了再也不喜歡綠色,而後把它從列表中刪除。

當你提交更新的時候,會發現 CVS 報出了個問題:

$ cvs commit -m "Remove green"
cvs commit: Examining .
cvs commit: Up-to-date check failed for `favorites.txt'
cvs [commit aborted]: correct above errors first!

這看起來像是朋友們首先提交了他們的變化。因此你的 favorites.txt 文件版本沒有更新到存儲庫中的最新版本。此時運行cvs status 就能夠看到,本地的 favorites.txt 文件副本有一些本地變動且是 1.2 版本的,而存儲庫上的版本號是 1.3,以下示:

$ cvs status
cvs status: Examining .
===================================================================
File: favorites.txt     Status: Needs Merge
   Working revision:    1.2 2018-07-07 10:42:43 -0400
   Repository revision: 1.3 /Users/sinclairtarget/sandbox/colors/favorites.txt,v
   Commit Identifier:   2oZ6n0G13bDaldJA
   Sticky Tag:      (none)
   Sticky Date:     (none)
   Sticky Options:  (none)

你能夠運行cvs diff 來了解 1.2 版本與 1.3 版本的確切差別:

$ cvs diff -r HEAD favorites.txt
Index: favorites.txt
===================================================================
RCS file: /Users/sinclairtarget/sandbox/colors/favorites.txt,v
retrieving revision 1.3
diff -r1.3 favorites.txt
3d2
< green
7,10d5
<
< pink
< hot pink
< bubblegum pink

看來咱們的朋友是真的喜歡粉紅色,但好在他們編輯的是此文件的不一樣部分,因此很容易地合併此修改。跟git pull 相似,只要運行 cvs update 命令,CVS 就能夠爲咱們作合併操做,以下示:

$ cvs update
cvs update: Updating .
RCS file: /Users/sinclairtarget/sandbox/colors/favorites.txt,v
retrieving revision 1.2
retrieving revision 1.3
Merging differences between 1.2 and 1.3 into favorites.txt
M favorites.txt

此時查看 favorites.txt 文件內容的話,你會發現你的朋友對文件所作的更改已經包含進去了,你的修改也在裏面。如今你能夠自由的提交文件了,以下示:

$ cvs commit
cvs commit: Examining .
/Users/sinclairtarget/sandbox/colors/favorites.txt,v < -- favorites.txt
new revision: 1.4; previous revision: 1.3

最終的結果就跟在 Git 中運行 git pull --rebase 同樣。你的修改是添加在你朋友的修改以後的,因此沒有 「合併提交」 這操做。

某些時候,對同一文件的修改可能致使衝突。例如,若是你的朋友把 「green」 修改爲 「olive」,同時你徹底刪除 「green」,就會出現衝突。CVS 早期的時候,正是這種狀況致使人們擔憂 CVS 不安全,而 RCS 的悲觀鎖機制能夠確保此狀況永不會發生。但 CVS 提供了一個安全保障機制,能夠確保不會自動的覆蓋任何人的修改。所以,當運行cvs update 的時候,你必須告訴 CVS 想要保留哪些修改才能繼續下一步操做。CVS 會標記文件的全部變動,這跟 Git 檢測到合併衝突時所作的方式同樣,而後,你必須手工編輯文件,選擇須要保留的變動進行合併。
這兒須要注意的有趣事情就是在進行提交以前必須修復併合並衝突。這是 CVS 集中式特性的另外一個結果。而在 Git 裏,在推送本地的提交內容以前,你都不用擔憂合併衝突問題。

標記與分支

因爲 CVS 沒有易於尋址的提交對象,所以對變動集合進行分組的惟一方法就是對於特定的工做目錄狀態打個標記。

建立一個標記是很容易的:

$ cvs tag VERSION_1_0
cvs tag: Tagging .
T favorites.txt

稍後,運行 cvs update 命令並把標籤傳輸給 -r 標誌就能夠把文件恢復到此狀態,以下示:

$ cvs update -r VERSION_1_0
cvs update: Updating .
U favorites.txt

由於你須要一個標記來回退到早期的工做目錄狀態,因此 CVS 鼓勵建立大量的搶先標記。例如,在重大的重構以前,你能夠建立一個 BEFORE_REFACTOR_01 標記,若是重構出錯,就可使用此標記回退。你若是想生成整個項目的差別文件的話,也可使用標記。基本上,現在咱們慣常使用提交的哈希值完成的事情都必須在 CVS 中提早計劃,由於你必須首先有個標籤才行。

能夠在 CVS 中建立分支。分支只是一種特殊的標記,以下示:

$ cvs rtag -b TRY_EXPERIMENTAL_THING colors
cvs rtag: Tagging colors

這命令僅僅只是建立了分支(每一個人都這樣以爲吧),因此還須要使用 cvs update 命令來切換分支,以下示:

$ cvs update -r TRY_EXPERIMENTAL_THING

上面的命令就會把你的當前工做目錄切換到新的分支,但《Pragmatic Version Control Using CVS》一書其實是建議建立一個新的目錄來房子你的新分支。估計,其做者發如今 CVS 裏切換目錄要比切換分支來得更簡單吧。

此書也建議不要從現有分支建立分支,而只在主線分支(Git 中被叫作 master)上建立分支。通常來講,分支在 CVS 中主認爲是 「高級」 技能。而在 Git 中,你幾乎能夠任性建立新分支,但 CVS 中要在真正須要的時候才能建立,好比發佈項目。

稍後可使用 cvs update 和 -j 標誌將分支合併回主線:

$ cvs update -j TRY_EXPERIMENTAL_THING

修正

有人告訴我,有不少組織企業,特別是像作醫療設備軟件等這種規避風險類的企業,仍在使用 CVS。這些企業中的程序員經過使用一些小技巧來解決 CVS 的限制,例如爲幾乎每一個更改建立一個新分支以免直接提交給 HEAD。 (感謝 Michael Kohne 指出這一點。) via: https://twobithistory.org/2018/07/07/cvs.html

相關文章
相關標籤/搜索