看板 tails
作者 tails (QQ)
標題 [教學] Git使用教學 Part 2 -- 控制版本
時間 2012年03月11日 Sun. AM 04:44:19


Part 1的部分主要實現了回朔及追縱的功能
可是名為版本控制,那到底要怎麼控制不同版本?
網路上常看到alpha、beta、stable等不同的版本
總不可能開三個目錄用Git管理吧

所以現在要講解的是branch(分支)
一圖勝萬言
值得一提的是:Git允許建立無限層的branch

相信上面那幾張圖已經說明非常清楚了
不過我還是用文字說明一下好了
Git預設的分支,同時也叫主幹的是 master
假設master已經release出1.0正式版並上線了
現在要開始開發新的功能
開發到一半時,發現1.0正式版有重大bug需要修正

好了,先看到這裡
如果是以前遇到這種情況的話
開發新功能通常就是做一份copy
然後開始開發
遇到重大bug時,因為開發未完成
所以只能拿正式版1.0版做修正,並繼續上線
然後就完了!!
修正完的檔案跟開發中的檔案
如果是用完整copy的話,通常無法得知到底是哪幾個新增/修改/刪除
如果是使用差分編碼,是可以知道,不過要是兩邊檔案遇到衝突時 (如同時都做了修改)
這時該怎麼辦?

Git的解決方法就是使用branch
同樣的狀況
master正式上線
然後從master建立一個branch叫develop來做開發新功能的branch
做了這個branch的動作時,它會把master目前的repository快照新建一份到develop
然後我們就可以對develop做修改,而且不影響master
遇到重大bug修正時
可從master建立branch叫hotfix來做修正bug
OK
到目前為止都和以前沒什麼兩樣 (除了它使用的是快照,而不是完整copy)
再來就不一樣了
hotfix修正完後
Git可以做merge(合併)的動作,把hotfix修正的內容合併到master和develop上
如果遇到衝突,也會一一顯示出來讓你檢視並做人工修正

可能上述例子還不夠震憾到非用Git不可
比較好的例子可能就是在多人多工的狀況下吧
假設團隊開發新功能的有5個人,通常都是5個人分別開發5種不同的功能吧
好啦,如果沒有Git,相信要檢查衝突的動作可以花上半天的時間吧
有了Git,快一點的話,可能只要花半分鐘就merge好了

如此說明應該可以了解branch的好用性了吧


-------------------- Branch教學 --------------------

從Part 1的例子繼續延伸
現在有test test2兩個檔案
從這master分支再建立新的分支叫develop
建立分支使用git branch指令

$ git branch develop master  #由master產生develop branch

使用git branch不加參數可以看有幾個分支

$ git branch
  develop
* master

打星號(*)指的就是你目前處於哪個分支
切換分支使用git checkout指令

$ git checkout develop
Switched to branch 'develop'
$ git branch
* develop
  master

再來我們來修改test檔

$ echo develop > test
$ git add test
$ git commit -m "I modified test in develop branch"
[develop 07e0c57] I modified test in develop branch
 1 files changed, 1 insertions(+), 0 deletions(-)

然後從master建立hotfix分支

$ git branch hotfix master
$ git checkout hotfix
Switched to branch 'hotfix'
$ cat test
$

這時可以發現test是空的
那這次我們來修改兩個檔
然後外加一個檔 test3

$ echo fix1 > test
$ echo fix2 > test2
$ touch test3
$ git add test
$ git add test2
$ git add test3
$ git commit -m "I fix some bug in hotfix branch"
[hotfix a955355] I fix some bug in hotfix branch
 2 files changed, 2 insertions(+), 0 deletions(-)
 create mode 100644 test3

再來我們再回去develop分支裡

$ git checkout develop
Switched to branch 'develop'
$ ls
test  test2

沒錯!!test3不見了!!
所以可以了解develop與hotfix是完全不相干的 (master也是)
那再來下一個問題就是如何merge回master
因為就順序來說
hotfix應該是先完成
然後再merge回master和develop裡的
首先我們先merge回master

$ git checkout master
Switched to branch 'master'
$ git merge --no-ff hotfix
Merge made by recursive.
 test  |    1 +
 test2 |    1 +
 2 files changed, 2 insertions(+), 0 deletions(-)
 create mode 100644 test3

這樣hotfix修改過的記錄就會合併到master了
再來我們來合併到develop看看會發生什麼事

$ git checkout develop
Switched to branch 'develop'
$ git merge --no-ff hotfix
Auto-merging test
CONFLICT (content): Merge conflict in test
Automatic merge failed; fix conflicts and then commit the result.

因為剛剛是故意讓test裡面放不同的內容
現在它就說有衝突需要解決完再做commit
來看一下test裡面的內容:

$ cat test
<<<<<<< HEAD
develop
=======
fix1
>>>>>>> hotfix

從 <<<<<<< HEAD 到 ======= 的中間區域是目前你所在 branch 的 commit 內容
從 ======= 到 >>>>>>> hotfix 則是你要合併的 hotfix branch 的內容
你可以選擇要留的部分或是改成你想要的內容
如果很多檔案需要改的話
可以使用git status查看

$ git status
# On branch develop
# Changes to be committed:
#
#
	
modified:   test2
#
	
new file:   test3
#
# Unmerged paths:
#   (use "git add/rm <file>..." as appropriate to mark resolution)
#
#
	
both modified:      test
#

改好後要再重新add後才能commit
這次commit會自動加上預設的訊息

$ git add test
$ git commit
[develop cbd29f5] Merge branch 'hotfix' into develop

引用gogojimmy的處理步驟:
1.將發生 confict 的檔案打開,處理內容( 別忘了刪除<<<、===、>>> )。
2.使用 git add 將處理好的檔案加入 stage。
3.反覆步驟 1~2 直到所有 confict 處理完畢。
4.git commit 提交合併訊息。
5.完成




到目前為主
都是以順利進行工作的情況來教學
但是實際工作上一定會出現很多狀況需要刪減
甚至是需要到回朔以前的狀態
gogojimmy:
版本控制最大的好處之一就是讓你永遠可以後悔
 (當然前提是你的commit要做的好)

以下就講解如何操作

$ touch test4
$ git add test4
$ touch test5
$ git add test5
$ git status
# On branch develop
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#
	
new file:   test4
#
	
new file:   test5
#

這裡看到有兩個檔準備要commit
假設事後發現test5還沒改好
所以現在想取消追縱再重新修改
那下這個指令 (其實上面也有說明)

$ git reset HEAD test5
$ git status
# On branch develop
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#
	
new file:   test4
#
# Untracked files:
#   (use "git add <file>..." to include in what will be committed)
#
#
	
test5
$ git commit -m "add test4 file"
[develop 0309c8b] add test4 file
 0 files changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 test4

如果已經在stage狀態的檔案修改後,是需要再重新add的
不然會出現像這樣的訊息

$ git add test5
$ echo "123" > test5
$ git status
# On branch develop
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#
	
new file:   test5
#
# Changes not staged for commit:
#   (use "git add <file>..." to update what will be committed)
#   (use "git checkout -- <file>..." to discard changes in working directory)
#
#
	
modified:   test5
#

這時重新add即可

$ git add test5
$ git status
# On branch develop
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#
	
new file:   test5
#
$ git commit -m "add test5 file"
[develop c3ab4f1] add test5 file
 1 files changed, 1 insertions(+), 0 deletions(-)
 create mode 100644 test5

如果上次一的commit訊息打錯的話
可以下這個指令去修正

$ git commit --amend

再來要玩玩回朔功能了
回朔指令是使用git reset
它有 --hard 或 --soft 參數可以加
參考gogojimmy的教學:
共同:都會把目前狀態回復到你想回復的版本
預設:會把你做過的修改仍然保留
soft:會把做過的修改加入 stage
hard:把做過的修改完全刪除,回到那個版本原本的樣子。

來玩看看吧!
HEAD後面可以加參數
HEAD^ 指的是目前版本的上一個版本
HEAD~2 指的是目前版本的上兩個版本(依此類推)

$ git reset --hard HEAD^
HEAD is now at 0309c8b add test4 file
$ git status
# On branch develop
nothing to commit (working directory clean)
$ ls
test  test2  test3  test4

因為是使用hard
所以test5也一起刪除了 (早知道先用預設....Orz)
我們也可以使用git log查一下上一個版本是做了什麼更改
再考慮要不要reset

$ git log | head -n 5
commit 0309c8bdc4f3d45d02a040f9d28722f8d5249627
Author: tails <tails@example.com>
Date:   Sun Mar 11 15:01:33 2012 +0800

    add test4 file
$ git reset HEAD^
$ ls
test  test2  test3  test4
$ git status
# On branch develop
# Untracked files:
#   (use "git add <file>..." to include in what will be committed)
#
#
	
test4
nothing added to commit but untracked files present (use "git add" to track)

回到未加入stage的狀態
再來再試看看 --soft 有什麼不一樣

$ git add test4
$ git commit -m "add test4 file"
[develop b145357] add test4 file
 0 files changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 test4
$ git reset --soft HEAD^
$ git status
# On branch develop
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#
	
new file:   test4
#

以下用圖解表示:

   前一個commit   >> git reset --hard HEAD^
       v
       v
       v
Untracked files  >> git reset HEAD^ #預設
       v
       v
       v
     stage       >> git reset --soft HEAD^
       v
       v
       v
     commit      >> #現在的狀態



基本branch的控制相信大家會操作了
可是感覺好像還少了什麼東西對吧?
假如我們想比對stable的v1.0版和stable的v1.1版有何不同該如何做?
或是想查v1.0版時的某個檔案長怎樣該如何做?
這就是接下來要提到的tag了
嚴格來說,其實是不一定需要tag就能做到上面所說的
review一下part 1所說的一句話
一個commit就是一個節點

所以我只要知道這次的commit是v1.0版
那次的commit是v1.1版就行了
但如果要切換的話一定要一個唯一識別碼
就像身份證字號一樣,一個號碼只能代表一個人
commit的識別碼也是,一個識別碼只會代表一個commit
只是commit的識別碼不會像身份證字號一樣簡單
在使用git log的時候就會看得到

$ git log
commit cbd29f531f88aeadc2aa55f35409ca36e920aaf8
....
(下略)

好樣的識別碼這麼長,該怎麼記
所以就像TCP/IP出現DNS一樣 (鳥哥的DNS server)
Git也出現tag
DNS的概念是 Name (好記) 對應 IP (難記)
tag的概念是 Tag (好記) 對應 hash (難記)
這樣應該比較好懂吧!

現在來實作看看
首先最簡單的指令git tag <tagname>
它會為當前已完成commit命名為 v1.0

$ git tag v1.0

也可以使用 -m 或 -a 選項加上註解
差別在於 -m 跟commit一樣,單行的註解
-a 是開啟編輯器

$ git tag -m "Version 1.0" v1.0
$ git tag -a v1.0

需注意的是,同個tag名不能重復使用,就跟識別碼的功用一樣
不加任何選項可以查詢現有的tag

$ git tag
v1.0

刪除已存在的tag使用 -d 選項

$ git tag -d v1.0

也可以針對過去已存在的commit作標籤

$ git log --pretty=oneline
cbd29f531f88aeadc2aa55f35409ca36e920aaf8 Merge branch 'hotfix' into develop
a955355ecef376e839343943f604e11c8033b898 I fix some bug in hotfix branch
07e0c57832c6ca76dc3b9a0028ecf9bf387736d8 I modified test in develop branch
b21501d19c3a05f1e79540b4297e3a97e8c1e9eb add test2 file
5f880bb87b78b8bb95c8f294506020671ed51455 add test file

$ git tag v0.1 5f880bb87b78b8bb95c8f294506020671ed51455
$ git tag v0.1 5f880bb  #只打前七碼也行

可以使用 git show 查看該版本的訊息

$ git show v0.1
commit 5f880bb87b78b8bb95c8f294506020671ed51455
Author: tails <tails@example.com>
Date:   Sun Mar 11 02:23:59 2012 +0800

    add test file

diff --git a/test b/test
new file mode 100644
index 0000000..e69de29

或是剛剛有提到的
查看該版本的某個檔案長什麼樣

$ git show v0.1:test
$ git show v1.0:test
fix develop

當標上標籤後
許多指令就能使用已標好的標籤來代表該commit了
(ps:剛剛在做標籤是接在git reset的狀態下,所以最後一次操作是merge)



--
下一篇Part 3 要進入多人開發的階段了
--
參考網頁
gogojimmy - Git教學(2)
小惡魔 - [Git] 版本控制: 如何使用標籤(Tag)
TKG - Git使用手冊
--
※ 作者: tails 時間: 2012-03-11 04:44:19
※ 編輯: tails 時間: 2012-03-16 18:19:07