转自 http://gogojimmy.net/2012/01/17/how-to-use-git-1-git-basic/
Git 是一套分散式的版本控制系統,版本控制是一個開發團隊中不可或缺的工具,Git 最強大的一個特點就是可以無窮無盡的開 branch (分支),好處就是今天不論是修 Bug ,開發新功能,或是研究 feature 都非常的方便,學 Git 到現在大概三個月的時間讓我體會到” Git 用的好,產品開發沒煩惱!!” ,搭配 Github (一個以 Git 作為基礎的程式碼社群服務,上面有非常多的資源)使用更是天下無敵,團隊開發怎麼能少的了用 Git 呢!!!!
坦白說 Git 還是需要一些時間去學習, Workflow 對於使用 Git 有非常大的影響,一個好的 Workflow 會更幫助你學會如何去操作 Git 及培養好的團隊開發技巧,因此這篇就是要來幫助剛學習 Git 的人如何快速上手 Git 及一個開發的 Workflow ,以下是給完全沒接觸過的新手的入門文,如果你想要快速的了解基本操作,你可以直接看這裡。
網路上已經有很多教學了,善用 Google 我相信你可以找到非常多的資源,的我建議可以參考 Github 上的教學(請先建立一個 Github 的帳號,因為他會連 Github 的設定一起設定完成):
若你想看中文的推薦你 Git 的權威教材 Pro Git。
Git 也有很多的 GUI 工具,免費的像是 GITX、 SourceTree、SmartGit (我沒用過但是是2011評價最好的 Git GUI),付費的像是 Tower 都是不錯的選擇,但我會建議先從基本的 Git 指令開始學習,當你對基本的指令熟悉後再去使用各種不同的 GUI 就會更加的得心應手了!
在每一次的 Git commit (提交,我們稍後會提到) 都會記錄作者的訊息像是 name 及 email ,因此我們使用下面的指令來設定:
$ git config --global user.name "Jimmy Kuo" $ git config --global user.email "[email protected]"
加上 --global 表示是全域的設定。你可以使用 git config --list 這個指令來看你的 Git 設定內容:
$ git config --list user.name=Jimmy Kuo [email protected]
或是其實 Git 的設定檔是儲存在你的家目錄的.gitconfig 隱藏檔中,你可以使用編輯器將他打開
$ cat ~/.gitconfig [user] name = Jimmy Kuo email = [email protected]
使用終端機來操作 Git 常會讓人覺得一直打指令很繁瑣,因此 Git 也有提供 alias 的功能,例如你可以將 git status 縮寫為 git st,git checkout 縮寫為 git co 等,你只要這樣設定:
$ git config --global alias.st status
這樣一來只要打 git st 就等同於打 git status 了。
空白對有些語言是有影響的(像是Ruby),因此我們會希望 Git 去忽略空白的變化,這時候你需要在你的設定加入:
$ git config --global apply.whitespace nowarn
如此一來 Git 對於空白的變化便會忽略不計。
Git 預設輸出是沒有顏色的,我們可以讓他在輸出時加上顏色讓我們更容易閱讀:
$ git config --global color.ui true
要開始使用 Git 你必須先建立一個 Git 的 Repository,你可以把它想做是一個資料庫的意思,有兩種方法可以建立一個 Git 的 Repository:
例如我現在有個叫做 Animal 專案資料夾,我現在想要開始使用 Git 開始管理,因此我先將目錄切換到 Animal 底下後輸入git init
$ git init Initialized empty Git repository in /Users/Jimmy/Projects/Animal/.git/
這時你就會看到 Git 告訴你說已經在這邊建立好一個新的 Git Repository。
例如我們在 Github 上面看到人家的程式碼想要抓下來自己修改,或是團隊中別人的程式碼,這時候他們通常會有一個 Git 的檔案位置像是在 Github 的話你就會看到像下面的地方可以讓你複製 git 的 clone 位置:
將他複製起來後到你的目錄下輸入 git clone
$ git clone https://[email protected]/gogojimmy/Animal.git
如此便會將這個 Git Repository下載到我們的資料夾, git clone 預設會將下載的 git 存成一樣檔名的資料夾,如果你要更改成別的名稱的話只需要在網址後面加上你想要更改的名稱即可,像是:
$ git clone https://[email protected]/gogojimmy/Animal.git monkey
這樣子下載下來的 Repository 的名稱就會從原本的 Animal 變成 monkey 了。
在一個 Git 的 Repository 中你可以輸入git status來檢查目前 Git 的狀態
$ git status # On branch master # # Initial commit # nothing to commit (create/copy files and use "git add" to track)
這代表目前是一個乾淨的目錄狀態,沒有未被追蹤或是被修改的檔案, On branch master 表示你目前正在名為 master 的 branch 上,等等會再說明 branch 的功用。我們現在在這個目錄新增一個 test 檔案後,再使用git status 來查看:
$ git status # On branch master # # Initial commit # # Untracked files: # (use "git add ..." to include in what will be committed) # # test.rb nothing added to commit but untracked files present (use "git add" to track)
這時候你會看到我剛剛新增的 test.rb 檔案變成在 Untracked files (未被追蹤的檔案),表示過去在這個 Git Repository 中從未有這支檔案,是一支未被追蹤的檔案,我們要把這個檔案讓 Git 能夠追蹤的話,我們使用 git add 這個指令來把它加入追蹤:
$ git add test.rb $ git status # On branch master # # Initial commit # # Changes to be committed: # (use "git rm --cached ..." to unstage) # # new file: test.rb #
你可以看到原本 test.rb 還在 Untracked files 中,經過我們使用 git add test.rb 後他就變成了 Changes to commit,通常我們稱這個狀態叫做 stage ,修改過但還沒使用 git add 的檔案稱為 unstage 。
如果你一次修改了很多檔案,懶得一個一個輸入 git add 的話,你可以輸入 git add . 這會幫你將所有剛剛修改過或新增加的檔案一次 Add 進 stage 狀態,但強烈不推薦這樣的作法,這樣的方法雖然方便但很容易會不小心加入一些其他不必要的檔案,一個正確的觀念是你必須隨時都很清楚你的檔案狀態,因此最好是手動將你確定要加入的檔案使用 git add來加入,有一個更好的作法是使用互動模式 git add -i ,在互動模式下你可以方便的選擇你要加入的檔案,或是移除剛剛不小心加入的檔案 (revert)。
已經在 stage 狀態的檔案的下一步就是準備提交( commit ),commit 是寫程式時一個很重要的動作,一個 commit 在 Git 中就是一個節點,這些 commit 的節點就是未來你可以回朔及追蹤的參考,你可以想像就像是電玩遊戲時的存檔,每一個 commit 就是一次存檔,讓我們未來在需要的時候都可以回到這些存檔時的狀態。因此我們將剛剛做的修改提交:
$ git commit
此時你會看到畫面跳到你在 git 中設定的編輯器畫面:
# # Please enter the commit message for your changes. Lines starting # with '#' will be ignored, and an empty message aborts the commit. # On branch master # # Initial commit # # Changes to be committed: # (use "git rm --cached …" to unstage) # # new file: test.rb #
這個視窗中最上面的空行是要給你寫下你這次 commit 的訊息,例如在這裡寫下 “Add test.rb file to test git function”來表示這一次提交的目的,強烈建議在 commit 的時候要盡量清楚表達這次 commit 的內容為何,因為這會讓你未來要回頭看 code 的時候能讓你快速的找到你想要找的內容,也能對團隊中其他成員了解你在做什麼。
在 commit 完後會顯出出你這次 commit 的更動:
[master 5f76371] add ignore files 2 files changed, 8 insertions(+), 0 deletions(-) create mode 100644 .gitignore create mode 100644 test.rb
如果你覺得每次這樣跳出編輯器很麻煩,你也可以在 commit 時加上 -m 的參數來快速提交:
$ git commit -m "Add test.rb to test git function"
若使用 -am 的話還能將所有未被 add 的檔案一併 add 進來( 更新:如果是第一次新增還沒有被 add 的檔案是不會一起加入的,只有之前已經被 add 過 commit 的檔案才會被加入 ),但就像前面所說這樣的作法並不推薦,你應該清楚的加入你應該加入的檔案就好:
$ git commit -am "Add test.rb to test git function"
若使用 -v 的話會列出更動的紀錄:
# Please enter the commit message for your changes. Lines starting # with '#' will be ignored, and an empty message aborts the commit. # On branch master # Changes to be committed: # (use "git reset HEAD ..." to unstage) # # new file: .gitignore # new file: test.rb # diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..61521a9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.DS_Store +*.swp +log/*.log diff --git a/test.rb b/test.rb new file mode 100644 index 0000000..23df1c1 --- /dev/null +++ b/test.rb @@ -0,0 +1,5 @@ +class Test + def test + puts "This is a test file" + end +end
加號(+)代表增加的部份,減號(-)代表刪除的部份。
我們可以使用 git log 的指令查看過去 commit 的紀錄:
$ git log 5f76371 - (HEAD, master) add ignore files (3 minutes ago) 4ca268d - (origin/master) First commit (33 minutes ago)
前面的亂碼代表的是當次 commit 的版號,後面的是 commit 的訊息、時間以及 commit的作者,你可以使用 --stat 參數看到更詳盡的訊息:
$ git log --stat 5f76371 - (HEAD, master) add ignore files (7 minutes ago) .gitignore | 3 +++ test.rb | 5 +++++ 2 files changed, 8 insertions(+), 0 deletions(-) 4ca268d - (origin/master) First commit (37 minutes ago) lib/animal.rb | 5 +++++ spec/animal_spec.rb | 5 +++++ 2 files changed, 10 insertions(+), 0 deletions(-)
如果你想看到檔案更詳細的變更內容,你可以加上 -p 的參數:
$ git log -p 5f76371 - (HEAD, master) add ignore files (8 minutes ago) diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..61521a9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.DS_Store +*.swp +log/*.log diff --git a/test.rb b/test.rb new file mode 100644 index 0000000..23df1c1 --- /dev/null +++ b/test.rb @@ -0,0 +1,5 @@ +class Test + def test + puts "This is a test file" + end +end
因此我們稍微做一下流程的整理:
修改檔案 => 加入 stage (git add) => 提交( git commit )=> 繼續修改其他檔案
什麼時候才是 commit 的最好時機並沒有一個定論,大部分人會告訴你通常你完成了一個階段性的小工作就做一次的 commit ,我的感覺是當你想要記錄目前的狀態的時候就是你 commit 的最佳時機,例如說剛完成某個page,某個任務需求而你想要做個記錄的時候。
有些檔案我們不希望加入版本控制的追蹤,例如說Database的schema或是一些log檔,這時候你可以將他們加入 .gitignore 中來讓 Git 忽略他們,使用編輯器來打開你的 .gitignore 檔案。
$ vim .gitignore
在這邊假設我想將 Mac 自動產生的.DS_Store, vim 的 swp 暫存檔及 log 檔忽略,我便分行加入:
.DS_Store *.swp log/*.log
如此一來便會將這些檔案從git追蹤忽略了。
有時候你會發現即時你將檔案加入了 .gitignore 卻一樣會出現在 Git 的追蹤狀態中,這是由於你想要忽略的檔案之前已經被 Git 追蹤了,因此你現在想要讓 Git 忽略他的話只能先將他刪除然後再 commit 一次,再來這支檔案再出現就不會再追蹤這支檔案了。
branch (分支)應該是 Git 最重要的技能了,在一個多人專案的開發過程中我們有時候要開發新功能,有時候是要修正某個Bug,有時候想要測試某個特異功能能不能 work ,這時候我們通常都會從主 branch 再開出一條新的 branch 來做,這支新開的 branch 會帶著你的主 branch 目前的最新狀態,當你完成你所要開發的新功能/ Bug 修正後確認沒問題就再把它 merge(合併)回主 Branch ,如此便完成了新功能的開發或是 Bug 的修正,因此每個人都可以從主 branch 拉一條新的 branch 來做自己想做的事,再來我們好好了解一下 branch 的使用。
了解 branch 最好的方法就是有圖像可以看,你可以用任何你已安裝的GUI來查看,但在你安裝 Git 的時候其實同時也安裝了最基本的 Git GUI 叫做 gitk,你可以使用 gitk --all 這個指令來呼叫他,此時你應該能看到以下的畫面:
點擊左上方區塊的 commit 節點你可以看到當次 commit 的詳細資料,例如作者以及他的更新記錄,但你會發現這時候終端機是無法輸入的,因此請你先關掉 gitk ,改打指令 gitk --all & 來讓 gitk 在背景執行。
git branch 這個指令可以列出所有的 branch 並告訴你目前正在哪個 branch:
$ git br * master develop feature/test
上面的訊息告訴我們在這個 Git repository裡有3支 branch ,而你目前正在 master branch 上。假設我們現在要開一支新的 branch 叫做 cat ,使用 git branch 來幫助你開一支新的 branch
$ git branch cat $ git branch cat * master
上面我們開了一支新的 branch 叫做 cat ,使用 git branch 再查看一次發現已經多了這支新的 branch了,這時候你去查看你的 gitk 的圖像狀態會發現像下圖一樣,新的 branch cat 與 master 在同一條水平線上,表示目前他們的狀態是一模一樣的。
你應該也有發現,雖然我們建立了一個 cat 的 branch ,但其實我們所在的 branch 還是在 master branch,因此我們現在還需要切換過去,因此我們使用 git checkout 來切換:
$ git checkout cat Switched to branch 'cat'
這樣就會從原本的 mater branch 切換到 cat branch 了。
接下來假設我正在 cat 這支 branch 做開發,因此新增一個檔案,加上一些內容,將它 add 到 stage 後再 commit 它。
$ vim lib/cat.rb $ git status # On branch cat # Untracked files: # (use "git add ..." to include in what will be committed) # # lib/cat.rb nothing added to commit but untracked files present (use "git add" to track) $ git add lib/cat.rb $ git commit -m "Add Cat.rb" [cat ea7d309] Add Cat.rb 1 files changed, 3 insertions(+), 0 deletions(-) create mode 100644 lib/cat.rb
上面的流程你已經很熟悉了,接下來我們再切換到原本的 master branch ,這時候你會發現剛剛在 cat branch 新增的 cat.rb 檔案已經不見了。
$ git checkout master Switched to branch 'master' Your branch is ahead of 'origin/master' by 1 commit.
Git 在我們切換 branch 的同時就會很聰明的會把我們的工作目錄更動成那個 branch 該有的狀態,如果你這時候切換到 GUI 去看,你會發現到與剛剛 cat 和 master branch 在同一條水平線上不同,cat branch現在已經比 master branch 再多出了一個 commit 的內容。
現在我在切換到 cat branch 去增加更多的內容,一樣再將它 add 到 stage 後,再 commit 它。
$ git checkout cat Switched to branch 'cat' $ mvim lib/cat.rb $ git add lib/cat.rb $ git commit -m "Add initializer" [cat a3bce42] Add initializer 1 files changed, 3 insertions(+), 1 deletions(-)
切到 GUI 來看的話你會發現現在 cat 這支 branch 比 master branch 要在多上兩個 commit 的內容。
如果這時候我們在 master 上繼續開發會發生什麼事呢? 我們現在切換到 master branch 並增加一個檔案及內容,照慣例 add 後 commit。
$ git co master Switched to branch 'master' Your branch is ahead of 'origin/master' by 1 commit. $ git add spec/animal_spec.rb $ git commit -m "Another spec" [master 7d72927] Another spec 1 files changed, 2 insertions(+), 0 deletions(-)
我在 master branch 的 animal_spec.rb 增加了一些內容,把它 commit 之後我們切換到 GUI 來看。
我們發現現在 master branch 與 cat branch 已經產生分歧了,因為兩支 branch 都有了各自往後開發的 commit ,而且由於 master branch 最後一次的 commit 時間較新因此排列在最前面。
假設我們現在在 cat branch 的開發動作已經完畢,通常我們現在要做的事情會是將 cat branch 合併回 master branch,在開發流程上, master branch 就像是一個主要的 branch ,每個開發人員都是從 master branch checkout 出去一支新的 branch 做開發,在開發完畢後就再將開發完的 branch 合併回 master branch,因此 master branch 都會保有最新的開發好的狀態,一般在 Git 教學中會教你現在使用 git merge 這個指令來將兩個 branch 合併,但這邊我要先教你的是 git rebase 這個指令。
與 git merge 不同的是, git rebase 不單單只是將兩個不同的 branch 合併起來,而是將某一支 branch 基於另一支 branch 的內容合併起來,這是什麼意思? 以我們的例子來說,我們在 cat branch 開發完了以後,這時候我們的 master branch 也有了其他開發者所合併回去的內容,換句話說現在的 master branch 與我們當初 checkout 出去的時候的狀態已經不同了,但我們會希望我們現在這支 cat branch 的內容就像是剛剛從 master branch checkout出來一樣乾淨,也就是說讓 cat branch 中保有 master branch 最新的狀態, git rebase 會基於 master branch 目前最後一次的 commit 內容再往後把你在 cat branch 上commit 的內容加上去,我們現在在 cat branch 輸入 git rebase master 來將 cat branch 基於 master branch 做 rebase。
$ git rebase master First, rewinding head to replay your work on top of it... Applying: Add Cat.rb Applying: Add initializer
過程中沒有發生衝突,這時候我們到 GUI 看看現在的結果。
原先 cat branch 上的兩個 commit (Add Cat.rb 和 Add initializer) 已經合併到 master branch 最新的 commit (Another spec),換句話說目前 cat branch 的內容就像是剛從 master branch 所 checkout 出來然後再加上自己的 commit,因此不同於 git merge 的線圖會把 cat branch 合併到 master branch , 而是把原本的 cat branch 接到 master branch 因此只有一條線,當一個專案有很多的 branch 再做開發的時候會避免很多 branch 的線接來接去難以辨認。
開發過程中,若你在開發的 branch 功能比較多, commit 的量也比較多時,建議使用rebase將你現在的 branch 整理過再合併回主幹,這樣會產生較漂亮的線圖
若你想要看看目前的 branch 與其他 branch 有哪些差異,你可以使用 git diff 的指令去觀察,例如我現在想要看 master 跟 cat 這兩個 branch 的差異,我只要下:
$ git diff cat master diff --git a/lib/cat.rb b/lib/cat.rb deleted file mode 100644 index 1227d26..0000000 --- a/lib/cat.rb +++ /dev/null @@ -1,5 +0,0 @@ -class Cat < Animal - def initialize - super - end -end
你就可以看到現在的 cat branch 跟 master branch 的差異在哪了。
如果我們開發完畢時,我們會把開發好的東西合併回 master 很自然的我們通常都會使用 git merge 這個指令來合併兩個branch
$ git merge cat Merge made by recursive. lib/cat.rb | 3 +++ 1 files changed, 3 insertions(+), 0 deletions(-) create mode 100644 lib/cat.rb
這時候我們看圖的話會是這樣:
可以看到我剛剛在 master branch 下了 git merge cat 這個指令來告訴 git 要 merge cat 到現在所在的 branch ,因此在圖上就看到了 cat branch 拉一條線回來合併到了 master 這個 branch 了,解釋這張圖的意思就是, cat branch 從 master branch 的 Another spec 這一次的 commit 分支出來後,自己產生了三次的 commit (Add Cat.rb、Add initializer、Rever “Add initializer”) 然後合併到 master。
很常發生的情況是再 merge 或是 rebase 的圖中產生了 convict (衝突),這時候 Git 會停下來請你去處理,例如我們在 cat 和 master 的 branch 都對 lib/cat.rb 這支檔案做編輯,然後我們將他們 merge:
$ git merge cat Auto-merging lib/cat.rb CONFLICT (content): Merge conflict in lib/cat.rb Automatic merge failed; fix conflicts and then commit the result.
你會看到 Git 告訴你在你合併的過程中在 lib/cat.rb 這支檔案發生了衝突,Git 不知道該怎麼處理因此要你去處理它,這時候我們打開這支檔案會看到這樣的情況:
<<<<<<<<<< HEAD 到 ========== 的中間區域是目前你所在 branch 的 commit 內容,而從 =========== 到 >>>>>>>>>>> cat 則是你要合併的 cat branch 的內容,你必須做出決定看是要兩個都留下,或是選一個,或是改成你想要的內容,改好後記得要將 Git 自動產生的 <<< 、 =====、 <<< 的內容都刪除,修改完畢後存檔,將剛剛修改過的檔案再使用 git add 加入 stage ,將所有的衝突都修正完畢後就使用 git commit 提交一次 commit,由於這次的 commit 是在處理 merge 時的衝突,因此 Git 很聰明的已經幫我們加上了一些預設的訊息 “Merge branch ‘cat’”, commit 提交後就會看到合併成功的訊息了。
$ git add lib/cat.rb $ git commit [master c37c9e3] Merge branch 'cat'
發生 confict 時的處理步驟
講到這裡我們再來整理一下工作流程,讓大家再複習一下 git 的使用:
版本控制最大的好處之一就是讓你永遠可以後悔,因此我們常會希望把已暫存的檔案、已提交的 commit 或是已合併的 branch 取消修改,這時候我們可以使用 git reset 這個指令來幫助我們,像現在我若是想要取消剛剛的 merge 動作,我只要下:
$ git reset --hard ORIG_HEAD HEAD is now at c126ff9 Config initialze
這時候再回去看圖你會發現已經回到合併前的樣子了:
有時候手殘不小心將還沒修改完的檔案使用 git add 加入了 stage ,這時候可以使用 git reset HEAD
你可以看到我使用 git add 將檔案加入 stage 後,在我的 status 狀態顯示 lib/cat.rb 這支檔案現在已經準備好被 commit ,但這時我使用了 git reset HEAD 將這支檔案取消 stage,再使用 status 查看時它就變回一支還沒加入 stage 的檔案了。
連續剛剛的情況,若是我想完全放棄這次修改 (將檔案狀態回復到最新的一次 commit 時的狀態),我可以使用 git checkout --
$ git checkout -- lib/cat.rb
取消變更不會有任何訊息,但這時你去看檔案會發現他已經回復成沒修改過時的模樣了。
手誤打太快, commit 訊息打錯時,我們可以使用 git commit --amend 來幫助我們重新修改:
在上面我想要修改打錯字的 commit 訊息 “Cat initiae”,因此我使用 git commit --amend 來修改成正確的訊息。
有時候我們想要放棄所有修改回到 commit 時的狀態,這時候我們可以下 git reset --hard HEAD 來回復,HEAD 參數可以加上一些變化,例如 HEAD^ 表示目前版本的上一個版本 HEAD~2 則是再上一個,因此你可以自由的跳回去之前的狀態。
你可能會在這邊感到疑惑,在使用 git reset 的時候都會看到一個 soft 或是 hard 的參數,這代表什麼樣的意義?基本上在使用 git reset 的時候,都會把目前狀態回復到你想回復的版本,但若是不加參數的情況,會把你做過的修改仍然保留,但是,若是加上 –soft 參數,則會把做過的修改加入 stage ,若是加上 hard 參數的話則是把做過的修改完全刪除,回到那個版本原本的樣子。
Mac : 安裝 Homebrew
brew install git
如何設定 Git
如何開始一個 Git Respository
如何將檔案加入 Stage
如何將檔案從 Stage 中移除(取消add)
如何將檔案提交(commit)
如何修改/取消上一次的 commit
分支基本操作(branch)
遠端操作(remote)
合併操作(merge)
暫存操作(stash)
常見問題: