Git輕鬆入門1:本地倉庫篇
阿新 • • 發佈:2021-02-02
## 什麼是Git
### 版本控制系統
首先我們要明白,什麼是版本控制系統(version control systems)?
版本控制系統,顧名思義,就是能控制檔案處於哪個版本的系統。
e.g. 你在部落格園裡編輯的文章,你可以控制文章是處於某個時間點的版本。
而Git就是一款版本控制系統,而且是分散式的。
與分散式相對應的是集中式版本控制系統,它的版本庫是儲存在中央伺服器的。工作的時候你需要先從中央伺服器取得最新的版本,等工作完了,再把自己的工作推送回中央伺服器。但這有個問題:萬一哪天中央伺服器崩潰了甚至資料丟失了,那所有人不就都無法工作了?
於是分散式版本控制系統(Distributed Version Control System,簡稱 DVCS)就應運而生了。它沒有“中央伺服器”,每個人的電腦上都有一份完整的版本庫,如果某個人的電腦壞了導致版本庫丟失了,直接從別人那複製一個版本庫過來即可。
### Git的基本工作原理
有一類版本控制系統是基於差異(delta-based)來進行版本控制的。
這類版本控制系統儲存的是新檔案和舊檔案間的區別(delta)。這裡的delta和高中物理裡的delta一樣,$Δv = v_2 - v_1$,都指的是變化量。
而Git和上述這類版本控制系統的工作原理不同:每當你對提交新專案或者對專案進行修改,它會把專案的所有檔案都壓縮儲存起來,這個壓縮檔案在Git中被稱為快照(snapshot)。每次有更新就會每次把所有檔案都壓縮起來儲存一遍,形成一系列快照。
當我們需要處於某個時間點版本的檔案時,直接根據索引,找到那個時間點的快照即可。
## 操作指令
在開始介紹Git相關的指令前,我們先建立三個檔案。
### 建立版本庫
所謂“版本庫(repository)”就是一個用來儲存檔案歷史版本的倉庫。所以要想對檔案進行版本控制,我們需要先初始化一個版本庫來儲存檔案的各個歷史版本。
`$ git init` 命令能把當前所在目錄變為由Git管理的倉庫(或是對該位置已有倉庫進行初始化),使得該目錄下的檔案可被Git進行版本控制。
當執行完該命令後,可以看到該目錄下生成了一個 `.git` 的資料夾。
### 把檔案新增到版本庫
建立完版本庫後,我們還需要把檔案放進去才能對其進行版本控制。
而把檔案新增到版本庫需要兩個步驟:
**第一步**,用 `$ git add` 命令把需要跟蹤版本的檔案新增到暫存區。
**第二步**,用 `$ git commit` 命令告訴倉庫:我確定要對暫存區裡的檔案進行版本控制,請把暫存區裡的檔案正式放入倉庫管理。
`-m "xxx"` 是對本次提交的備註說明;`2 files changed` 指有兩個檔案變動:即我們新新增的 `README.md` 和 `demo1.txt`;`1 insertion` 指插入1行內容:即我們在 `README.md` 檔案中插入的“Git is a version control system.”。
在每次提交變動(commit)後,都會產生對應的快照。快照,我們在Git的工作原理那章提到過,它就是被放入版本庫中的所有檔案的壓縮版本。我們可以把快照理解成是遊戲的存檔。在玩遊戲時,我們每通過一關就會儲存遊戲。如果進行到某一關死了,那就可以通過讀取存檔來回到前面的某一關。Git也一樣,每次commit就是在給檔案存檔,要是哪一天檔案誤刪了或出錯了,那我們就可以讀取存檔,讓檔案恢復到存檔時的狀態。
通過 `$ git log` 命令,可以檢視當前共有幾個存檔。
### 同步修改到版本庫
當我們在工作區(工作區就是當你開啟“我的電腦”看到的目錄就叫工作區)裡對檔案進行修改後,如何把更新同步到版本庫中,讓版本庫也擁有檔案最新的版本呢?
我們先新增一行話到 `README.md` 檔案中,使其內容變為如下:
```markdown
Git is a version control system.
Git is distributed.
```
現在,執行 `$ git status` 命令檢視版本庫當前狀態:
從狀態中我們可以得到2個資訊:
1. 對 `README.md` 檔案進行了修改,並且這次修改還沒有被新增到暫存區。
2. 在 `learn_git` 目錄下,`demo2.txt` 檔案沒被新增到倉庫中以進行版本追蹤。
如果我們想要進一步得知對 `README.md` 檔案進行了哪些修改,可以用 `$ git diff` 命令(這步並不是必須的)。
可以看到,新增加的改動是“Git is distributed”這行話。
確定了作出哪些修改後,再把它提交到倉庫自然就放心多了。**提交修改和之前提交新檔案需要同樣的兩個步驟:`$ git add` 和 `$ git commit`。**
我們先執行 `$ git add`,然後看看版本庫狀態。
可以看到,當我們用 `$ git add` 命令把檔案新增到暫存區後,版本庫狀態就不會再提示我們“尚未暫存已備提交的變更”,而是提示“要提交的變更”,即提示我們要用 `$ git commit` 命令提交這次變更。
那我們就再執行一下 `$ git commit` 命令,再來看看版本庫狀態。
再用`git log`檢視一下當前共有幾個存檔(快照)。
從以上兩張截圖可以看出現在已經沒有需要提交的修改了,只剩下未跟蹤的檔案 `demo2.txt` 了,剛才的那次變動已經被版本庫存檔了。
### 把檔案從版本庫中刪除
刪除檔案也算是對檔案的一種修改。同樣也需要兩步:
1. 用 `$ git rm ` 把該檔案從工作區刪除。
2. commit這次變動,把刪除同步到版本庫。
注:在執行 `$ git rm ` 指令時,Git會預設執行 `$ git add` 命令來把這次修改提交到暫存區。
### 回退到某個版本
我們已知目前共有2個存檔:第一個存檔的ID是`b1a6863...`,存檔備註是“added README and demo1”;第二個存檔的ID是`245fcc1...`,存檔備註是“modified README”。
在Git中,`HEAD` 是一個指標,它會指向當前所在分支的最新存檔,分支的概念我們後邊在介紹,但反正目前我們沒有建立別的分支。你讓 `HEAD` 指向哪個版本號,Git就把當前版本定位在哪。所以此時 `HEAD` 代表的就是ID為`245fcc...`的存檔,那麼在它前一個的存檔就是 `HEAD^`,前N個的存檔就是 `HEAD~N`。
有一天你可能會覺得第二個版本很糟糕,不如第一個版本,於是想坐時光機回到過去,讓一切復原到第一個版本時的樣子。這時我們可以用 `$ git reset` 命令來幫助我們穿越時空。
我們再來看一下 `README.md` 裡的內容,後面新新增的那行就不見了,內容回到了最初的時候:
```markdown
Git is a version control system.
```
再執行一下 `$ git log`。
一切真的都回到了過去!連存檔都變成和當時一樣,只有`b1a6863...`這一個了!
為什麼回退的速度這麼快呢?
因為我們每個版本的檔案都被儲存在了快照裡,Git僅僅只需要把 `HEAD` 指標從一個快照指向到另一個快照,再把目錄裡的檔案替換成那個快照裡的檔案即可。
所以建議:無論是大的變動還是小的變動,都存一下檔(即commit)。只有把這個版本存檔了,儲存進快照裡了,日後才能通過讀檔來返回到該版本。
可萬一你又後悔了,覺得一切還是像第二個版本時的那樣好,怎麼辦?通過 `$ git log` 命令已經找不到第二個版本的存檔了,是不是回不去了?
並不是的。在Git中,你的每次操作就會留下記錄,我們可以用 `$ git reflog` 來檢視所有的操作。
從中我們可以看到,備註為“modified README”的那次commit的ID是`245fcc1`。有了commit ID後,我們就又能通過 `$ git reset` 命令回到未來了!
再執行一下 `$ git log` 和開啟 `README.md` 檔案,可以確認真的回到了未來!
### 撤銷修改
撤銷修改有三種情況:
1. 修改還沒新增到暫存區。
2. 修改已經新增到暫存區,但還沒commit。
3. 修改已經commit。
#### 修改還沒新增到暫存區
我們先談第一種情況。
假設你往 `README.md` 檔案裡添加了一行“My boss is stupid. ”,而這最新修改還沒有被你新增到暫存區。所以當你執行`$ git status` 命令時,你看到Git提示你有尚未暫存已備提交的修改。
你覺得這句話可能會讓你被炒魷魚,儘管你說的是事實。於是你最終還是決定把這句話刪掉。
有兩種刪除方式:
1. 手動開啟 `README.md` 檔案,然後把這句話刪掉,讓檔案回到新增這行話前的樣子。
2. 按照Git提示的,用 `$ git restore ` 命令,來直接撤銷改檔案在工作區的變動。
你覺得第二種方式看起來顯得逼格更高點,於是你輸入如下命令。
```zsh
$ git restore README.md
```
然後當你再開啟README.md檔案,發現那句話果然消失了;再執行 `$ git status` 命令,發現Git也沒有任何提示了。
#### 修改已經新增到暫存區但還沒commit
再來談談第二種情況:你不僅寫了那句話,還提交到了暫存區!所以當你執行 `$ git status` 命令時,你看到Git提示你有要提交的變更。
有了上次撤銷的經驗,你就放心地按照Git的提示輸入如下命令:
```zsh
$ git restore --staged README.md
```
再執行 `$ git status`,你發現現在它提示的和第一種情況一樣了,而開啟工作區裡 `README.md` 檔案,那句話還在。原來這個命令只是幫你撤銷一小步,僅僅是從暫存區移除,而不是幫你一夜回到解放前。
當然了,如果你很不幸的已經commit這次提交了,那就只能用 `$ git reset` 來版本回退了。
## 總結
Git中檔案的所有狀態以及狀態間的轉換可以被概括為下面這張圖。
最後,總結一下今天出現過的幾個Git命令:
```zsh
# 建立版本庫
$ git init
# 把檔案新增到暫存區
$ git add
# 把暫存區裡的變動提交到版本庫
$ git commit
# 檢視當前所有快照
$ git log
# 檢視版本庫當前狀態
$ git status
# 檢視某檔案的變動
$ git diff
# 刪除檔案,並把這次變動提交到暫存區
$ git rm
# 版本間的前進和回退
$ git reset
# 撤銷修改
$ git restore
```
## 參考
1. https://sp18.datastructur.es/materials/guides/using-git.html
2. https://www.liaoxuefeng.com/wiki/896043488029600
3. http://git-scm.com/book/en/v2/Getting-Started-What-is-Git%3F
4. https://stackoverflow.com/questions/4964099/what-is-a-git-snapshot
有問題歡迎大家在評論區留言,轉載請註明