第五章 git
git 是一個在軟體開發上的版本控管系統 (Version Control System), 早期流行的 CVS、svn (subversion) 也都是屬於同類型的系統。它主要是用來控管程式碼 (檔案) 的版本。當你的程式碼在持續開發時, 每一次修改都會被記錄。要對照不同的版本、時間點、作者, 都非常好用。而通常它也是很好的備份方案, 因此如果誤刪檔案, 還可以很快速的救回來。
這邊只會講到非常基礎與簡單的用法。現在網路上有非常多資源在說明 git, 這邊就只針對最基礎的部分說明。
建立一個 repository
現在雲端的 git service 很方便, 如 Github、Bitbucket 或是 GitLab 都是很常見的服務。我們會以 GitLab 為例, 但是其實你使用其他的服務也都大同小異。
首先, 在我們開程式專案時, 先建立一個 repository (倉庫)。在 GitLab 裡面找到「New Project」就可以開啟一個新的專案。
在 Project name 填上專案的名稱, 再按下 Create Project 即可
至於到底要如何命名你的專案名稱呢? 目前看到的都是以「全部小寫」以及「-」連接為主。可以參考下面兩篇文章:
- github - Is there a naming convention for git repositories?
- github - Is it suggested to have Git repository names in upper or camel case instead of lower case?
(或是直接搜尋「git repo naming conventions」)
新增認證金鑰
建立好專案之後, 我們需要做一個類一次性的動作 (取決於你會使用多少不同的電腦來存取這個 git repository), 那就是建立與新增金鑰到 GitLab 上。
首先要先建立金鑰, 但在建立之前, 有可能之前已經建立過了, 所以先看看檔案在不在
$ ls ~/.ssh/id_rsa.pub
如果出現的訊息是
ls: /Users/yychen/.ssh/id_rsa.pub: No such file or directory
那就是還沒有建立過, 等等需要建立。如果出現的訊息是
/Users/yychen/.ssh/id_rsa.pub
那代表已經有這個檔案, 可以不用建立金鑰 (也就是可以跳過下面「建立金鑰」的步驟)。
建立金鑰
建立金鑰的指令很簡單, 只要輸入 ssh-keygen
這個指令即可
$ ssh-keygen
它會先問要金鑰要存放在哪裡, 你只要按下 Enter, 選擇它預設的值即可
Enter file in which to save the key (/Users/yychen/.ssh/id_rsa):
接著, 它會要求你建立一個密碼 (以及再打一次確認)
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
如果你此時打了密碼, 則你每一次使用到這個金鑰時都需要輸入密碼才能成功。你也可以按下 Enter 留白, 就不會建立密碼。之後使用它的時候也不用每次都輸入。
此時, 你的金鑰就已經建立完成。金鑰包含兩個部分, 分別是公鑰與私鑰, 他們分別會被存在 ~/.ssh/id_rsa.pub
以及 ~/.ssh/id_rsa
這兩個檔案裡面。
新增金鑰到 GitLab
我們先把我們剛建立好的 (或是之前建立好的) 公鑰內容複製
$ cat ~/.ssh/id_rsa.pub
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDg44DR4HXmHS... [email protected]
把 ssh-rsa (包含) 開頭的整個好幾行的內容, 全部圈選起來複製。接著到 GitLab 的 Profile Settings 裡面的 SSH Keys。將內容貼在 Key 的框框裡面, 然後再按下 Add Key 即可。
在自己的電腦上作業
我們在 GitLab 上面已經新增好專案了, 那現在就要讓自己的電腦上跟這個專案做連結。不過我們還有一個一次性的設定要在我們自己的電腦上面做, 那就是設定 git 的使用者姓名與 E-mail。在你的 git 安裝好之後, 只要設定一次即可。在 command line 鍵入下面指令
$ git config --global user.name "Tom Chen"
$ git config --global user.email "[email protected]"
當然, 上面的 Tom Chen
與 [email protected]
記得要換成你自己的。
接著, 就要跟 GitLab 上面的專案與自己的電腦上的東西做連動了。連動的方式有兩種, 第一種是你什麼東西都還沒有, 也就是還沒有任何程式碼任何檔案, 那我們會直接將 GitLab 上剛建立的 repository 給 clone (複製) 下來。第二種則是你已經有一些程式碼了, 我們會初始化你放程式碼的目錄, 並且設定與 GitLab 上的專案連結。關於第二種的設定方式, 請參考本章下方「已經有程式碼了, 但 git 是新開的」。
在開始之前, 先找到 repository 的位址並且複製下來。它就位於專案首頁的框框裡面。如果框框的前面寫著 HTTP, 請改成選擇 SSH。HTTP 也是可以使用, 但缺點是你會需要一直輸入帳號密碼, 很不方便。
然後再 command line 輸入下列指令
$ git clone [email protected]:ctchen1/test-project.git
clone 後面就是剛剛複製下來的位址。下了這個指令它就會把 test-project 抓下來放在 test-project 這個目錄裡面了。
新增檔案到 Repository
在 git 裡頭, 每一次的變更我們會稱作為 commit。你可以想像它是一個批次 - 這個批次包含一個或數個檔案的變動。舉例來說, 你有可能修正某一個 bug, 而修正的當中會有好幾個不同的檔案的變更, 那我們會將這一整個變更稱作為 commit。當然, 也有可能你只是新增一個檔案, 而這個 commit 就只有包含這一個檔案。
到底多少的變動要 commit 一次進去, 大家都有很多的討論, 這不會是這邊要講的內容, 但他確實是一個被探討的議題。關於這個議題, 可以參考:
- Commit Often, Perfect Later, Publish Once: Git Best Practices
- How often to commit changes to source control?
- 5 Reasons for Keeping Your Git Commits as Small as You Can
我們現在先來建立一個新的檔案叫做 README.md, 內容如下
# README
我的第一個檔案
編輯好之後, 我們要用 git add [file]
的指令將這個檔案加到這一次的 commit 裡頭
$ git add README.md
接著, 我們可以用 git status
來看目前的狀態
$ git status
On branch master
Initial commit
Changes to be committed:
(use "git rm --cached <file>..." to unstage)
new file: README.md
你可以看到它寫說準備要被 commit 的變更有「新檔案: README.md」。
我們這一次變更就只要這一個, 因此我們再用 git commit
這個指令來把確認我們這一個 commit
$ git commit -m 'First commit. Add README.md'
上面指令的 -m ...
代表的是這一次變更的註解。註解其實很重要, 它有助於在事後翻閱變更紀錄時, 了解我們這一次做了什麼事情。想要瞭解更多關於如何寫好 commit message, 可以參考
最後, 我們要將我們現在的狀態推上 GitLab, 我們會用到 git push
這個指令
$ git push origin master
其中 origin 代表 repository 的來源, 也就是 GitLab, 而 master 則是預設的分支 (branch)。所以這個指令就是把 code 推到 origin (GitLab) 的 master 分支。
當你這個步驟做完的時候, 你就會發現 GitLab 上面這個專案的首頁有所改變, 你所打的 README.md 的內容會依照 Markdown 的格式顯示在專案的首頁下方。
你也可以按「Repository」的欄位, 或「Files」去看這個 repository 底下有哪些檔案
.gitignore
在 git 裡頭, 我們可以編輯 .gitginore
這個檔案來告訴 git 哪些檔案應該被 git 忽略掉。忽略的原因不外乎我們不希望某些類別的檔案進到 git 裡面。
怎麼樣的檔案我們不希望進到 git 呢? 譬如說編譯好的執行檔、物件檔, 或是第三方套件, 或是編輯器用的暫存檔。簡單來說, 如果原始碼最後可以編譯出執行檔, 那我們就只留原始碼, 當要使用這個專案的開發人員把程式碼拉下來後, 再自己編譯即可。而第三方套件的部分, 如 node.js 的慣例是會有 node_modules
目錄會直接存放這個專案需要的第三方套件, 而通常可以直接透過 package.json
這個檔案來安裝所有第三方套件, 那麼我們就是留 package.json
進 git, 而不留 node_modules
。
因為不同程式語言、框架, 你的 .gitignore
可能會有所不同, 以 Python 的專案為例, .gitignore
可能內容如下
*.pyc
*.swp
其中 .pyc
是被編譯過的 bytecode, 而 .swp
則是 vim 的暫存檔。
Node.js 的 .gitignore
可能就會長得像這樣
*.swp
node_modules
也有一些很酷的服務可以自動產生相對應的 .gitignore
, 譬如 gitignore.io。或者是你可以自己搜尋 gitignore 以及你所使用的語言、框架。
當我們編輯好 .gitignore
之後, 他也是可以被進到 git 裡面的。如果你現在只是練習, 那麼你可以新增一個 .gitignore
的檔案, 內容如下
*.swp
接著, 我們可以再簡易的複習上面的流程
$ git add .gitignore
$ git commit -m 'Add *.swp to .gitignore'
$ git push origin master
把 .gitignore
加入這次的 commit 之後, 就 commit, 然後把它推到遠端去。
變更檔案
如果現在我們更改了 README.md, 將內容變成
# Test-Project
這是一個測試專案, 好棒棒!
更改後, 我們下的指令一樣是 git add
, 將這個檔案加到這次的 commit 裡面
$ git add README.md
最後一樣, 將它 commit 之後推回去
$ git commit -m 'Change content of README.md'
$ git push origin master
目前看起來好像就是 git add
, git commit
然後 git push
。當然, 你可以 git add
好幾個檔案, 也就是這個 commit 會包含多個檔案變更, 然後才 commit。然後你也可以好幾個 commit 之後, 才 push 回去。不過當然, 儘早 push 回 GitLab, 因為難保有什麼萬一, 例如你的硬碟壞掉, 誤刪檔案, 或是有人在此時也進 code 到時候會產生衝突 (conflict)。
刪除檔案
如果你有一個檔案要從 git 裡面刪掉, 則你需要用 git rm
這個指令。例如, 我們想要把 README.md 刪掉, 則需要打下列指令
$ git rm README.md
後續動作則跟上面一樣, 將刪除的這個動作進到這次的變更, 然後 push 回去
$ git commit -m 'Delete README.md'
$ git push origin master
更新
當然既然你會做變更, 然後推回去, 那麼同樣的事情也有可能會有別人做。因此每次我們開始要更改程式碼之前, 最好先將目前的 repository 從 GitLab 拉下來。而這個動作很簡單, 就是透過 git pull
這個指令
$ git pull origin master
是否跟推回去的指令很類似呢? :D
已經有程式碼了, 但 git 是新開的
如果你已經有一個專案, 它是已經有程式碼在裡面了, 但是卻沒有使用 git, 流程上會是如何呢? 首先, 最前面也是一樣, 先開一個 GitLab 的專案。
接著, 去到你本地端專案的資料夾, 假設是 ~/test-project
, 執行 git init
$ cd ~/test-project
$ git init
然後將這個資料夾跟我們 GitLab 上的專案做連結
$ git remote add origin [email protected]:ctchen1/test-project.git
其中 origin 後面的連結就是我們剛剛新開專案 GitLab 上面的連結。
接下來我們就準備把所有的檔案加進去, 但是在這之前, 我們最好先設定好 .gitignore
, 所以等等那些我們不想進去的資料夾與檔案不會進去。
等到設定好之後, 將所有的檔案加入, 並且 commit
$ git add .
.
的意思是當下的目錄, 也就是把目前這個目錄底下的所有東西都加進這一次的 commit。最後, 再把它推回去
$ git push origin master
基本工作流程
這邊提一個最基本的工作流程
git pull origin master
- 編輯檔案
git add xxx
或git rm xxx
git commit -m 'xxx'
git push origin master
也就是每次改之前先拉, 然後進行修改、變更, 然後 commit 之後推回去。