參考資料##
我是通過閱讀《Pro Git》這本書學習 Git 的,我讀的時候還是第一版的英文版,現在已經出第二版了,而且英文版和中文版都有。英文第二版的地址是 https://git-scm.com/book/en/v2,中文第二版的地址是https://git-scm.com/book/zh/v2。想看第一版的話把地址中的v2
改成v1
就可以了。如果哪天突然鏈接打不開了,也別着急,記住《Pro Git》這個招牌,使用搜索引擎很快就能找到它。
Git 是什么##
Git 是一個版本控制系統,它可以保存工作文件的所有修訂版本。有了它你就可以將某個文件回溯到之前的狀態,甚至將整個項目都回退到過去某個時間點的狀態,你可以比較文件的變化細節,查出最后是誰修改了哪個地方,從而找出導致怪異問題出現的原因,又是誰在何時報告了某個功能缺陷等等。使用版本控制系統通常還意味着,就算你亂來一氣把整個項目中的文件改的改刪的刪,你也照樣可以輕松恢復到原先的樣子。但額外增加的工作量卻微乎其微。
Git 的特點:
- Git是分布式的版本控制系統,它沒有中心服務器的概念(雖然實際開發中可以建一個中心服務器),每一台開發機器上都保存完整的歷史記錄;但是它有本地代碼倉庫和遠程代碼倉庫的概念(不然怎么多人協作?),而且可以追蹤多個遠程倉庫。
- Git 在沒有網的情況下也可以工作。Git在本地磁盤上保存有項目的完整歷史,Git 中的絕大多數操作都只需要訪問本地文件和資源,一般不需要來自網絡上其它計算機的信息。所以 Git 能夠非常快地建立分支和合並分支,並具有強大的跟蹤分支和切換分支的能力。大部分操作看起來瞬間完成。有網的時候,再向遠程倉庫 push 一下就行了。
- Git 一般只添加數據。你執行的 Git 操作,幾乎只往 Git 數據庫中增加數據。 很難讓 Git 執行任何不可逆操作,或者讓它以任何方式清除數據。一旦你提交數據到 Git 中,就難以再丟失數據,特別是如果你定期的推送數據庫到其它倉庫的話。
下面是我簡化了的 Git 使用小結,圖片都來源於《Pro Git》第一版,因為第二版我也沒看完。
每一個項目都應該有一個工作目錄(Working directory),我們可以自己建一個目錄,然后把這個目錄里面的代碼用 Git 管理起來(使用git init
命令和git add
命令),也可以通過git clone
命令從別的地方克隆一個項目過來自動生成一個工作目錄。在工作目錄中的文件就是當前編輯和修改的文件,如果是新建立的目錄或新克隆來的目錄,工作目錄中的文件就是該項目最新的狀態。Git 是在本地保存有所有的歷史記錄和分支記錄的,這些內容都在工作目錄的.git
目錄中,稱之為本地倉庫(local repository)。當切換分支或查看以前的歷史版本時,工作目錄中的文件自動改變(這才是重點,工作無需切換目錄,目錄中的文件會自動切換)。工作目錄中的文件有三種狀態:已修改(modified)、已暫存(staged)、已提交(committed)。修改后的文件可以先加入暫存區域,一次工作結束后一起提交。
Git是分布式的,沒有中心服務器的概念,但實際工作中仍然可以把代碼倉庫放到一台大家都可以訪問的服務器上,做實際的中心服務器使用(僅在小團隊時使用此工作流程,原因后面詳述)。在本地機器上工作完后,使用git push
命令把倉庫推送到服務器上,換一個地方換一台機器后,只需要git clone
一下,又可以獲得所有的代碼(包含所有的歷史記錄及分支)繼續工作。服務器故障也沒問題,因為每一個工作的機器上都保存有完整的代碼倉庫,所以從不用擔心代碼丟失。沒有網絡也沒有關系,在本地機器上照樣可以提交(git commit
),因為整個倉庫就在自己的機器上,當有網絡時,git push
一下就可以了。
Git有遠程倉庫(remote repository)的概念,而且可以管理很多個遠程倉庫,遠程倉庫可以是服務器,也可以是別人的個人計算機(但一般沒有人這么用),每一個遠程倉庫都有一個簡短的名字和一個地址,最開始用git clone
克隆代碼的那個遠程倉庫別名往往默認為 origin,自己添加的遠程倉庫可以隨意指定別名,當然所有的遠程倉庫都可以隨意修改別名。可以從遠程倉庫獲取代碼(git fetch
命令或git pull
命令),也可以把自己的代碼推送到遠程倉庫(git push
命令,需要寫權限)。
既然 Git 即可以隨便從遠程倉庫獲取代碼,又可以把自己的代碼推送到遠程倉庫,那么當多人協作時,豈不會亂套嗎?解決這個問題的,就是 Git 的必殺之技——創建分支及分支合並。
首先,隨着一次次的提交,在本地代碼庫中形成一個主分支,如下圖:
有時為了開發新特性,隨時可以開一個新分支,如下圖:
新分支和主分支之間可以隨意切換,隨着分支的發展,形式如下圖:
主分支也可以向前發展,如下:
最終,當新分支代碼很穩定以后,可以將其合並到主分支,如下圖:
而能夠防止多人協作時出現混亂的關鍵就在於,當從遠程倉庫 clone 代碼庫到本地或 fetch 代碼庫到本地時,遠程分支的標記並不等於本地分支的標記。從遠程 clone 一個代碼庫到本地后,其 master 分支有兩個標記,一個標記為 origin/master 表示遠程庫中的 master 分支,一個標記為 master,表示本地的 master 分支。如下圖:
可以想象,由於別人的工作,遠程倉庫中的 master 分支肯定會向前繼續移動,但是在下次聯網之前,該 origin/master 標記不會移動。而本地的 master 標記繼續向前移動。
直到下次聯網,使用git fetch
命令將遠程倉庫的內容取回本地,origin/master 標記才會改變位置,這時,看起來就像是兩個分支,如下圖:
最后,將 origin/master 分支合並到 master 分支中(使用git merge
命令),本地代碼庫又一次變成了一個單一的 master 分支,繼續向前開發,並可以將它 push 到遠程倉庫,供別人使用。
Git沖突的處理完全靠人工完成。(從邏輯上講,機器也不可能完美處理沖突。)比如一個小型團隊一起工作,他們可以設置一個服務器用於保存遠程 Git 倉庫,然后每個人工作之前先從該遠程倉庫 fetch 代碼,接着工作,工作完成后,先在本地提交,最后 push 到遠程倉庫。但是當一個人 push 的時候,已經有人在他之前 push 了,如果他們工作在同一個分支,就會出現沖突。解決沖突的辦法就是先把別人 push 的內容再次 fetch 下來,合並分支,然后再 push。
通過前面對git原理的了解,可以分析得出使用Git時有以下幾種工作流程:
- 一個人單干,不需要考慮沖突,隨時可以開分支、合並分支和切換分支,隨時可以本地提交。如果為了防止代碼丟失,可以開一個服務器,每次工作完成就 push 到服務器上;
- 小型團隊合作,如前所述,開一個服務器保存代碼倉庫,然后所有的人把該服務器當成遠程倉庫,工作之前先 fetch,工作之后再 push。如果有沖突,則先 fetch,合並分支解決沖突后再 push。如果團隊人數太多,每個人都向該服務器 push,那沖突該是有多少?有可能一個開發者第一次向服務器 push 的時候,有人在他之前已經 push 過了,他只好先 fetch,手工合並解決沖突,可等他再次 push 的時候,發現又有人再他之前已經 push 了,於是他只好再做一次解決沖突的流程,可是如果在他工作的時候,又有人 push 了呢?這也是之前講的該工作流程只適合小型開發團隊的原因。
以上流程經過適當修改也可以供大型團隊使用,那就是將團隊分組,每個組的成員共用一個服務器當遠程倉庫,組長合並了該組的工作成果后,再push到另一個服務器當總的遠程倉庫,這樣就可以大大減少沖突的數量,減少工作量。 - 開源項目的合作,在這種情況下,每個人都把自己的倉庫暴露在互聯網上。開源項目的組織者或負責人將所有人的倉庫設為遠程倉庫,並把有意義的工作合並到主分支,然后發布官方的 Git 倉庫。每個開發者從官方倉庫 fetch 代碼后,完成自己的工作,然后再把它 push 到互聯網上自己的倉庫,等着項目負責人將自己的工作整合到官方倉庫中。如果項目負責人不干了,改人了,只要還有人繼續開發,該項目就可以繼續下去。碰到團隊比較大的情況,也可以進行分組。
Git 服務器的建設也相當簡單,因為 Git 支持以 SSH、HTTP 等協議傳輸數據,如果需要對服務器有寫權限,就開通 SSH 服務吧,設一個賬戶供所有人訪問 Git 倉庫即可。如果只需要讀權限,使用任何一個 HTTP 服務器均可。關於 Git 服務器的建設,請自行參考官方文檔。如果是個人的、開源的項目,可以使用 Github 網站提供的服務,直接存儲在互聯網上。(Github 私人倉庫是要收錢的。)
在 Eclipse 中使用 Git##
得益於 Eclipse 中的 EGit 插件,在 Eclipse 中使用 Git 非常簡單。任何一個項目,都可以使用快捷菜單中的 "Team" -> "Share Project" 將文件交給版本控制軟件管理,如下圖:
現在最流行的版本控制軟件當然是非 Git 莫屬了。Eclipse 會提示我們創建一個 Git 倉庫(Repository)。Eclipse 中的項目(Project)是一個比較小的概念,它不能等同於 Git 中的工作目錄(Working Directory),Git 中也沒有項目的概念,但是在 Git 的工作目錄中管理多個文件夾是沒有什么問題的,而 Eclipse 中的項目就是一個文件夾,所以,在 Git 的一個工作目錄中管理多個 Project 是沒有問題的。因此,只有 Eclipse 的工作區(Workspace)才等同於 Git 中的工作目錄(Working Directory)。而 Git 的倉庫(Repository)一般是放在 Git 工作目錄中的一個.git
目錄,考慮到 Eclipse 的工作區(Workspace)中本來就已經包含了很多元數據,再在里面創建一個.git
目錄,並且在里面存放 Git 的數據容易引起混亂,因此最好是把 Git 的工作目錄和倉庫創建在 Eclipse 的 Workspace 之外。幸好,Eclipse 支持這樣的功能。如下圖,點擊 "Create" 按鈕,創建一個 Git 倉庫,這里輸入的路徑/home/youxia/git/samples
其實是一個工作目錄:
從下圖可以看出,工作目錄為/home/youxia/git/samples
,Git 的倉庫為/home/youxia/git/samples/.git
,原本在 Workspace 中的項目 JavaIODemo 被自動移到了/home/youxia/git/samples
目錄中:
可以使用 Navigator 視圖查看項目中所有的文件,沒有被 Git 跟蹤的文件其圖標中顯示一個小問號。可以使用 "Ignore" 菜單項讓 Git 忽略對某些文件的跟蹤,比如源代碼編譯后產生的類文件,如下圖:
使用 "Add to Index" 菜單項讓 Git 對那些需要進行版本控制的文件進行跟蹤,如下圖:
進行提交,如下圖:
提交時需輸入 Commit Message,並指定 Author 和 Committer,可以查看此次提交涉及哪些文件。如下圖:
已提交的文件其圖標又會發生變化,如下圖:
再來看一下在一個倉庫中管理多個項目。再創建兩個項目,JaasDemo 和 SecurityDemo,點擊菜單項 "Share Project",在彈出的對話框中,不再選擇創建倉庫,而是選擇現有的倉庫,如下圖:
操作完成后,這三個項目都被同一個 Git 倉庫所管理。再有更多的項目都可以添加到這個倉庫中。我在 Github 中注冊了一個賬號,youxia 這個用戶名已經被占用了,所以我只能用 youxia-cn,cn 代表中國,其實這個賬號挺好記的。然后,我又創建了一個 samples 倉庫,用來存放我博客中寫的源代碼。以后大家需要參看我的源代碼的時候,只需要訪問 https://github.com/youxia-cn/samples 就可以了。
最后,把我本地的倉庫推送(Push)到 Github 中,如下三圖:
Clone、Pull 和 Fetch##
從遠程倉庫中獲取數據的方法有多種。如果是第一次獲取遠程倉庫,可以使用git clone
命令。在 Eclipse 中,需要使用 "File" -> "Import" 功能。下面換一台電腦,這台電腦安裝的是 Ubuntu 系統,啟動 Eclipse,點擊菜單 "File" -> "Import",獲取剛才存放在 Github 中的項目,在彈出的對話框中輸入正確的 URL 后,一路 Next,如下動圖:
可以使用 "Show in History" 和 "Show in Repositories View" 查看倉庫的詳細信息,如下兩圖:
從遠程倉庫獲取數據還可以使用git pull
命令,其對應的 GUI 操作界面如下:
可以看到,它會讓你選擇是將遠程倉庫中的分支 Merge 到本地的當前分支還是將本地的當前分支 Rebase 到遠程倉庫中的分支。使用 Pull 功能時,分支的 Merge 和 Rebase 是自動進行的,所以如果沒有准備好的話,不要輕易使用。
安全的做法是使用git fetch
命令從遠程倉庫獲取數據。Fetch 過來的分支不會自動合並,你可以切換到這些分支進行查看,然后手工進行合並。其操作界面如下:
Checkout、Reset、Merge 和 Rebase##
前面介紹過,隨着 Git 一次次的提交,會形成一個主分支,除此之外,還可以創建新的分支。通過git branch
命令創建新分支,通過git checkout
命令切換分支。在 Git 中,有一個HEAD
指針總是指向當前正在工作的分支,如下圖:
如果切換分支,HEAD
指針也會隨着移動,如下圖:
同時,還可以使用git reset
命令改變HEAD
指針的指向,以達到撤銷操作的目的。隨意改變HEAD
指針的指向是危險的,特別是使用git reset
命令的--hard
選項的話,它會使我們的部分工作丟失。在《Pro Git》的第二版中專門有一節“重置揭秘”對該命令及其涉及的原理進行了論述。在 GUI 中使用 Reset 也是非常簡單的,如下圖:
而分支的合並又有兩種方式,Merge 和 Rebase。Merge 比較簡單,如下圖:
在圖中有兩個分支 origin 和 mywork,origin 做了提交 C3 和 C4,mywork 做了提交 C5 和 C6。如果將這兩個分支合並的話,就會產生提交 C7,如下圖:
但是如果使用 Rebase 就不一樣了,如果把 mywork 分支 Rebase 到 origin 上,就相當於把 C5 和 C6 中針對 C2 所做的修改在 C4 上重演一次,產生了 C5' 和 C6' 兩次提交,而原有的 C5 和 C6 會被丟棄,如下圖:
所以,Reset 和 Rebase 都是比較危險的命令,有引起數據丟失的風險。但是在 Git 中,只要是曾經 Commit 過的數據,都是可以找回來的,可以參考《Pro Git》中“維護與數據恢復”這一節(第一版位於第 9 章,第二版位於第 10 章)。另外,《Pro Git》提到,對於已經公開發布到遠程倉庫中的代碼,不要使用 Rebase,否則會引起版本庫的混亂。
總結##
GUI 界面的使用沒有什么困難的,難的是理解 Git 中的概念,比如 Push、Pull、Clone、Fetch、Checkout、Reset、Merge、Rebase 什么的。學習 Git 最好的辦法,當然是認真閱讀《Pro Git》這本寶典。除了使用 GUI,最好也能多敲一下命令行,這樣對 Git 會有更加深入的了解。
(京山游俠於2016-10-05發布於博客園,轉載請注明出處。)