讀者可能會猶豫為什麼主講後端的文章會需要談到git,這是因為筆者認為在我們專案成形後,git具有協助我們做到版本控制及異地備份的功用。若要跟其他團隊中的工程師夥伴(不管前端、後端或APP端...等。)進行協同合作,也是非學不可的利器之一。
在一個系統的開發過程我們可能會經歷些設計階段。
如:雛型->上線前->測試->正式上線->維護->功能延伸…等。
在每個設計階段中可能會有不一樣的版本,而版本控制系統能夠提供給我們在進行專案時,能夠紀錄一個或一組檔案在某段時間內的變更。若是之後專案中的資料有損毀或是遺失等不可預期的問題發生,都能藉由該特性隨時隨地恢復專案在能正常運作的版本(在有正常使用版本控制系統的情況下)。
以git來說它是屬於版本控制系統中的「分散式版本控制系統」,其特性是將整個專案儲存為儲存庫(repository),也就是每當有做版本上的變動時等同於在備份整個專案。
「儲存庫」是git用來儲存整個專案的物件資料庫。通常我們可以使它來將整個專案進行備份、異地開發甚至是用此特性來與他人協作開發。
相較於「分散式版本控制系統」,另外還有「本地端版本控制系統」、「集中式版本控制系統」而這些系統的特性可參考關於版本控制這篇。
對於git除了上述所提到的儲存庫(repository)外,下述還會介紹工作目錄(working directory)、暫存區(index/stage)還有提交(commit)。
在我們專案中有可能會有許多的不一樣的版本,而「工作目錄」則是眾多版本中所取出的其中一個版本。而這些資料則是從「儲存庫」中取出,提供開發者進行開發或使用。
為一個單純的檔案,在「工作目錄」中若檔案有所更動,其檔案的更動狀態都會暫存在「暫存區」,以供後續「提交」階段所使用。
「提交」在git中就是一個節點,而這些節點就是開發者在未來想要回朔版本或追蹤版本的參考。也可以想像成每次在「提交」時,也就等同在進行一次儲存版本的動作。
在該部分,我們將會透過下圖中的各種不同情況來進行介紹,並針對各個情境來講述要用什麼樣的git指令。
A: 將一般目錄變成git working folder
$ git init
B: 將working folder的異動狀態登錄到stage
若是要將特定檔案(如readme.md)可使用git add <filename>
來進行。
$ git add readme.md
但若是不針對特定檔案,想要一次將全部有異動的狀態登錄到stage則可以使用git add .
的方式進行。
$ git add .
C: 將stage的狀態放到local repository
在輸入git commit -m
之後,我們可以在後面為該次commit增加異動說明,好用來提醒自己或是協同開發的夥伴們得知,每個commit之間做了什麼樣的變動。
$ git commit -m "first commit"
[master (root-commit) 6b56ee9] first commit
1 file changed, 1 insertion(+)
create mode 100644 readme.md
D: 將local repository的狀態放到remote repository
若是要將整個local repositry放到remote repository則可以使用git push
這個指令,此時就會將我們整個專案放到遠端的儲存庫,也等同於做了一次專案備份的動作。
$ git push -u origin master
Counting objects: 3, done.
Writing objects: 100% (3/3), 214 bytes | 0 bytes/s, done.
Total 3 (delta 0), reused 0 (delta 0)
To https://github.com/PenguinRun/TeaTime.git
* [new branch] master -> master
E: 將remote repository的狀態取回到local repository
若是要將repository更新到在remote repository的最新版本,則可以使用git pull
來進行。但若已經是在最新狀態則會出現Already up-to-date
的回應。
$ git pull
Already up-to-date.
F: 將local repository的狀態退回到stage階段(是C的逆向)
假設在commit輸入版本資訊時有誤,想要重新修改commit資訊就可以使用該指令。
該階段我們會透過git log
來觀看commit的版本歷程
$ git log
commit 6867a82c9d599d1f1e9384e3ef0bac5207afb474
Author: penguinrun <xxx@gmail.com>
Date: Sun Mar 26 15:00:19 2017 +0800
git test
commit 6b56ee9a804e1f464ae13e40a8cdd7fd750e14b6
Author: penguinrun <xxx@gmail.com>
Date: Sun Mar 26 14:56:52 2017 +0800
first commit
若說要把資訊為”git test”的commit修改成”black tea”,這邊有兩種方式可以實現。
可以先使用git reset --hard
來記錄我們要修正的commit的SHA值。之後在藉由git reset --soft HEAD@{1}
指令來實行。
$ git reset --hard d676f2c539c3adc2156b7ee8f76f26252de40e28
HEAD is now at d676f2c git test
$ git reset --soft HEAD@{1}
之後我們在透過git log
來觀看,就會發現到只剩下一支commit,而這支commit也就是我們最初新建時的那支。
$ git log
commit 6b56ee9a804e1f464ae13e40a8cdd7fd750e14b6
Author: penguinrun <xxx@gmail.com>
Date: Sun Mar 26 14:56:52 2017 +0800
first commit
那讀者應該會有個疑問是那剛剛修改的那支呢?
別擔心!只要在輸入git commit -m “black tea”
的指令後就會又出現了。這是因為git的狀態已經回覆到在我們還沒輸入commit資訊前,所以它並不會顯示在git log
中。而之後的版本狀態我們在透過git log
來觀看就可以看到原本消失的那一個又出現了,而且commit的版本資訊也改為”black tea”。
$ git commit -m "black tea"
[master 0d39456] black tea
1 file changed, 1 insertion(+)
create mode 100644 black_tea.js
$ git log
commit 0d394564a3f78bb5bc92f6f0ab8efd8c61fa669d
Author: penguinrun <xxx@gmail.com>
Date: Sun Mar 26 16:36:58 2017 +0800
black tea
commit 6b56ee9a804e1f464ae13e40a8cdd7fd750e14b6
Author: penguinrun <xxx@gmail.com>
Date: Sun Mar 26 14:56:52 2017 +0800
first commit
直接使用git reset --soft HEAD^
。這個指令中的”^”會直接針對我們在最後一支版本前的那一支直接進行更改,之後在輸入git commit -m “black tea”
就可以了。
$ git reset --soft HEAD^
$ git status
On branch master
Your branch is behind 'origin/master' by 1 commit, and can be fast-forwarded.
(use "git pull" to update your local branch)
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
new file: black_tea.js
$ git commit -m "black tea"
[master f7e6827] black tea
1 file changed, 1 insertion(+)
create mode 100644 black_tea.js
$ git log
commit f7e68273c4f9898e1686323f88232203db777ca4
Author: penguinrun <xxx@gmail.com>
Date: Sun Mar 26 17:19:25 2017 +0800
black tea
commit 6b56ee9a804e1f464ae13e40a8cdd7fd750e14b6
Author: penguinrun <xxx@gmail.com>
Date: Sun Mar 26 14:56:52 2017 +0800
first commit
G: 將stage的狀態退回到working folder階段(是B的逆向)
將在stage狀態的git 恢復到還沒執行git add
指令之前。
這時候我們先重新git pull
來將存放在remote repository的資料取回來可以發現到black_tea.js又回來了。
$ git pull
Updating 6b56ee9..6867a82
Fast-forward
black_tea.js | 1 +
1 file changed, 1 insertion(+)
create mode 100644 black_tea.js
並重新使用git log
來觀看目前commit的情形
$ git log
commit 6867a82c9d599d1f1e9384e3ef0bac5207afb474
Author: penguinrun <xxx@gmail.com>
Date: Sun Mar 26 15:00:19 2017 +0800
black tea
commit 6b56ee9a804e1f464ae13e40a8cdd7fd750e14b6
Author: penguinrun <xxx@gmail.com>
Date: Sun Mar 26 14:56:52 2017 +0800
first commit
這時,我們先藉由F的流程先將git的狀態挪至stage的狀態,並透過git status
來觀看結果:
$ git reset --soft HEAD^
$ git status
On branch master
Your branch is behind 'origin/master' by 1 commit, and can be fast-forwarded.
(use "git pull" to update your local branch)
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
new file: black_tea.js
接著在透過git reset HEAD
的指令來將black_tea.js還原至還沒進行git add
之前,並透過git status
來觀看結果:
$ git reset HEAD black_tea.js
$ git status
On branch master
Your branch is behind 'origin/master' by 1 commit, and can be fast-forwarded.
(use "git pull" to update your local branch)
Untracked files:
(use "git add <file>..." to include in what will be committed)
black_tea.js
nothing added to commit but untracked files present (use "git add" to track)
H: 將local repository的狀態一口氣退回到working folder階段
這時候我們先重新git pull
來將存放在remote repository的資料取回來,並重新使用git log
來觀看目前commit的情形:
$ git log
commit 6867a82c9d599d1f1e9384e3ef0bac5207afb474
Author: penguinrun <xxx@gmail.com>
Date: Sun Mar 26 15:00:19 2017 +0800
black tea
commit 6b56ee9a804e1f464ae13e40a8cdd7fd750e14b6
Author: penguinrun <xxx@gmail.com>
Date: Sun Mar 26 14:56:52 2017 +0800
first commit
這時,我們在嘗試使用看看git reset
會有什麼樣的效果並藉由git log
來觀看版本歷程看有什麼樣的變化:
$ git reset 6b56ee9a804e1f464ae13e40a8cdd7fd750e14b6
$ git log
commit 6b56ee9a804e1f464ae13e40a8cdd7fd750e14b6
Author: penguinrun <xxx@gmail.com>
Date: Sun Mar 26 14:56:52 2017 +0800
first commit
就會發現到原先的“black tea”已經不見了,這時我們可以透過git status
來觀看,就能發現到已經恢復到在我們進行git add
之前的狀態。
$ git status
On branch master
Your branch is behind 'origin/master' by 1 commit, and can be fast-forwarded.
(use "git pull" to update your local branch)
Untracked files:
(use "git add <file>..." to include in what will be committed)
black_tea.js
nothing added to commit but untracked files present (use "git add" to track)
I: 從remote repository取回建立成新的working folder
取出remote repository的資料,我們可以透過git clone
來實現,而這時就可以看到整個專案的檔案。
$ git clone https://github.com/PenguinRun/TeaTime.git
Cloning into 'TeaTime'...
remote: Counting objects: 6, done.
remote: Compressing objects: 100% (3/3), done.
remote: Total 6 (delta 0), reused 6 (delta 0), pack-reused 0
Unpacking objects: 100% (6/6), done.
Checking connectivity... done.
J: 將git working folder變回一般目錄
如果要將專案脫離git恢復成一般目錄則輸入rm -r .git
就可以。這時我們嘗試用git status
來測試,可以發現到它會告知現在已經沒有git的repository,也就是已經恢復成一般目錄了。
$ rm -r .git
$ git status
fatal: Not a git repository (or any parent up to mount point)
Stopping at filesystem boundary (GIT_DISCOVERY_ACROSS_FILESYSTEM not set).
基本的git運用介紹就到此結束。在這篇中運用到的指令其實都比較跟個人開發有關,而在下一篇中將會針對「branch」來介紹git的相關指令。
Revert to a commit by a SHA hash in Git?