上一篇中我們介紹了git的基礎概念,這篇我們就來說說分支
分支
git中的分支其實只是一個指標指向一個commit物件,而不是像傳統的版本控制系統一樣把整個當前版本複製一份出來。它背後其實就是一個檔案,我們可以去.git/refs/heads資料夾下面檢視,裡面的每個檔案其實就是一個分支,而內容其實就是一串SHA1值,而這個SHA1值又是什麼呢,其實就是一個commit id,以下圖為例,其實此時master分支檔案存放的就是master分支上的最後一次commit id,而分支背後代表的就是一條commit物件鏈。
而HEAD又是什麼呢,其實它也是一個指標,指向著是當前分支,而不是commit物件。在.git目錄下有一個HEAD檔案,裡面就是記錄著HEAD指向的分支。
分支合併
接下來就用一個例子來介紹一下git分支合併的過程,首先假設我們處於master分支上
接著我們從當前master上建立一個新分支dev,並且換到當前分支上git checkout -b dev
下一步我們在dev分支上修改並提交一個新的commit
接著我們回到master分支並執行git merge dev
合併分支
這裡要注意的是merge預設採用的是fast-forward快進模式,它代表著就是直接將master指標指向最新的commit id,中間master分支不會做任何的修改,而沒有建立任何新的commit,但是實際上不是任何情況下我們都可以直接這樣快進的,合併時修改的不是同一個檔案的同一個內容是可以直接fast-forward合併,但是因為兩個分支都有過commit,所以合併的時候就一定會產生一個新的commit物件,同時當我們修改的是同一個檔案同一個內容時,合併是會產生衝突的,這是後我們就必須要手動修復衝突,解決後通過git add標示衝突已解決並用git commit提交,也一樣不可避免會產生一個新的commit。在合併之後,反過來如果被merge的分支想要去merge已經merge完的分支,就直接fast-forward就可以了,以為兩條分支已經有了一個交匯點,所以直接快進至交匯點即可,所以這裡其實是可以總結一條規律的,什麼情況下可以快進呢,就是噹噹前的commit物件鏈可以從當前想要merge的分支走到要merge到的commit點,那就可以直接快進了。
那說完快進模式後,自然也就有非快進模式,非快進模式模式在上面提到的情況時即便沒有衝突,兩個分支也沒有同時進行修改也會產生一個新的commit,在我們merge時我們只要加上一個--no-ff
即可,git merge --no-ff dev
,那非快進模式的好處是什麼呢,其實就是如同下圖所示,非快進模式可以幫我們保留分支的樣子,在樹狀結構中可以完整的看出分支的情況
Reset
git reset其實有3中模式,mixed, soft和hard,預設不寫的話會呼叫mixed模式,那麼3者的區別是什麼呢,mixed其實就是會將reset之後的commit與原commit之間的修改轉到工作區,而soft則是轉到暫存區,最後hard就是直接丟棄掉,所以可以看出其實reset與它的字面意思有些不同的,我們經常會誤會它是重新設定把之後的commit砍掉,其實它只是回到之前的狀態。
Stash
git stash的作用是什麼呢,假設當我們在feature1上做事時,突然需要緊急去feature2上做事,此時就需要將feature1上的事情暫時用git stash先存起來然後去feature2上做事,做完再回feature1上通過git stash pop將之前儲存的修改取出。需要注意的是git stash會記住目前的commit id,所以如果同一個commit下stash兩次對於同一檔案同一行內容的修改,恢復完第一次並且提交過後,在執行第二次恢復就會有衝突。
Tag
tag就是我們之前提到的其實就是一個標籤物件,它也是指向某一個commit物件,它與分支的區別就在於當有commit發生時,分支會跟著一起向前移,但是標籤一旦定下來就不會再移動了,所以它通常是用於我們專案到了一個milestone,需要釋出一個新版本時使用。git標籤分為兩種輕量級標籤(lightweight)和附註標籤(annotated),就如同字面上的意思,輕量級於附註標籤的區別就是一個有描述資訊一個沒有。
Diff
git diff背後其實就是利用了linux自帶的diff模組用來比較檔案之間的差別,有興趣的可以去了解一下
指令
-
檢視分支列表
git branch
-
建立分支
git branch 分支名
-
切換分支
git checkout 分支名
-
切換到上次處於的分支中
git checkout -
-
刪除分支()
git branch -d 分支名
這裡需要注意不能刪除當前處於的分支,如果非master分支有改動但還未merge的話也不可以,除非使用
git branch -D 分支名
-
建立新分支並切換到新分支
git checkout -b 分支名
-
顯示當前分支最近的一條提交訊息
git branch -v
-
將分支合併到當前分支
git merge 分支名
-
禁用fast-forward,會多一個commit id
git merge --no-ff 分支名
-
回退到上一次提交(基於當前的commit)
git reset --hard HEAD^
-
回退到上上一次提交(都是基於當前的commit)
git reset --hard HEAD^^
-
回退到當前分支上的前n次(從當前commit往前n次)的提交
git reset --hard HEAD~n
-
回退到指定commit
git reset --hard commit資訊的前幾位
-
修改分支名
git branch -m 原分支名 新分支名
-
將工作區的修改儲存
git stash
-
列出所有的儲存
git stash list
-
手動設定stash描述
預設執行git stash返回的描述資訊是
其實就是包含了當前最新commit的訊息,那麼我們可以通過以下命令來修改描述資訊git stash save 'hello basic'
-
恢復最近一次的儲存,並且會把這次儲存在列表中刪除
git stash pop
-
恢復最近一次的儲存,但是不會在列表中刪除
git stash apply
-
apply特定一個版,並且會把這次儲存在列表中刪除
git stash apply stash@{0}
-
手動刪除指定的一個儲存版本
git stash drop stash@{0}
-
建立輕量標籤
git tag v1.0.1
-
建立附註標籤
git tag -a v1.0.2 -m 'release 1.0.2'
-
檢視標籤
git tag
可以檢視所有的標籤,同時也可以通過git tag show 標籤名
來看某個標籤 -
查詢標籤
git tag -l 'v1.0'
裡面可以使用pattern,例如'v*', 代表v開頭的所有標籤 -
刪除標籤
git tag -d 標籤名
-
列出每一行都是誰在什麼時間哪個commit修改的
git blame 檔名
-
比較算入暫存區修改的當前檔案與工作區檔案之間的區別
git diff
-
比較當前最新commit與工作區的區別
git diff HEAD
-
比較某個commit與工作區的區別
git diff commit_id
-
比較最新提交與暫存區的區別
git diff --cached
-
比較某個commit與暫存區的區別
git diff --cached commit_id
-
一次把本地還未提交的標籤推送上去
git push origin --tags
-
刪除遠端標籤
可以通過呼叫
git push origin --delete tag 標籤名
,或者使用git push origin :refs/tags/v6.0
推送一個空標籤上去 -
裁剪掉當前遠端分支
有時候可能使用者a執行了命令刪除了遠端的a1分支,那麼對於使用者b來說當他執行
git remote show origin
時,會發現遠端的a1分支是處於stale狀態,也就是說遠端已經沒有了,但是本地還在,這時候就可以執行git remote prune origin
,這樣就會把那個stale狀態下的遠端分支刪掉
場景
-
當我們使用
git reset --hard
指令回退到之前的commit時,執行git log
只能看到當前commit及其之前的commit,那如果我想回到之後的某個commit,我要怎麼獲取commit id呢?我們可以通過執行
git reflog
來檢視操作日誌,看到我們使用git的指令的歷史,這樣就可以找回我們之前的commit id了,這裡說下reflog背後其實就是記錄著HEAD指標的改變歷史,所以平常都會說不要手動去修改.git目錄下的檔案,因為手工修改之後是不會記錄在reflog中的 -
我不小心把一個分支用
git branch -D 分支名
強制刪除掉了,還救得回來嗎?是救得回來的,這裡要先強調的是,分支其實只是一個指標,所以即便刪除掉了這個指標,我們的commit物件還在,所以當然是可以救回來的,只要我們通過
git reflog
找到原本分支指向的commit-id,然後執行git branch 分支名 commit-id
就可以重新建立回我們的分支了。這裡在延伸說一下,其實在git merge
時背後其實也是在合併commit物件,所以我們其實也可以通過git merge commit-id
來做合併的,分支只是給我們一個類似貼紙一樣簡單標註了這串分支commit鏈做的是什麼。
Q&A
-
git checkout commit-id
與git reset --hard commit-id
有什麼區別呢?兩者都可以回到對應的commit點,但是checkout與reset不同的是它會處於遊離狀態,任何的修改如果不做提交就會有警告不允許我們跳去其他的commit,同時修改完我們可以通過
git branch 分支名 當前checkout的commit-id
來建立一個新分支。