今年下半年,中日合拍的《Git遊記》即將正式開機,我將...(上集)
一回首,2018年就剩下10天啦,看到很多巨佬都在發技術總結的文章,反觀自己這隻鶸(ruò), 技術沒啥長進,摸魚摸了一年,慚愧。琢磨著硬擼也要擼一篇總結的文章,不然有種2018白過的感覺。 至於選Git的理由:
- 看到掘金的童鞋發了好幾條小冊的票圈,想想自己第一本入的小冊是凱哥的:Git 原理詳解及實用指南, 和很多童鞋一樣,買了就當自己學了(看都沒看過),寫總結的的時候順帶看下咯。
- 之前也寫過一篇Git總結的文章,投掘金的處男稿,慘被拒,寫Git有種承前啟後的趕腳。
- 很多用Git的童鞋還停留在git pull和push等基本操作的程度。
行吧,上面的內容都是我「瞎編」的,「瞎編不是改編,胡說不是戲說
本來想一篇寫完的,不過內容著實太多了,索性就分開兩篇發了,上集講述的姿勢點:
Git概述——分散式版本控制系統
弄清楚Git相關的概念,有助於我們後續對於命令的掌握。
1. 什麼是版本管理系統
Version Control System,簡稱VCS,一種用於記錄一個或多個檔案內容變化 歷史,以便將來能對特定版本的歷史記錄進行檢視,更改,備份還原的系統。
可以類比成「遊戲存檔」,特定時間結點:比如打Boss前存檔,如果GG了,重新 讀檔就好了;還比如分支劇情,想體驗不同選擇觸發的不同的劇情,存多個檔,想玩 哪個檔讀哪個檔。
VCS一般分為以下三類:
- 1.本地VCS
使用簡單的資料庫來記錄檔案的歷史更新差異,比如RCS。
- 2.集中式VCS
用一個伺服器來儲存所有檔案的修訂版本,協同工作的人連線這個伺服器, 獲取或提交檔案更新,比如SVN。
這種協同方式有個兩個明顯的缺點: 1.「需要聯網」:同步和推送更新速度受頻寬限制,內網還好,外網可能會有點慢了(大檔案); 2.「依賴中央伺服器」:每個人的本地只有以前所同步的版本,如果伺服器宕(dang)機了,誰都無法獲取或提交更新。
- 3.分散式VCS
每個使用者擁有完整的提交歷史,支援離線提交更改,檢視歷史提交記錄等。中央伺服器更多的只是用作更改合併,同步的工具,比如Git。
從根本上來說,Git是一個記憶體定址的檔案系統,根據檔案的Hash值來定位檔案。 這個40位的Hash值使用SHA1演算法生成,由兩部分拼接: header = "<type>" + content.length + "\0" (引數依次為:物件型別,資料位元組長度,空位元組~用於分隔header與content) hash = sha1(header+content),這裡的拼接是二進位制級別的拼接,而非字串拼接。
2. Git與SVN的區別
Git和SVN除了上面說的聯網需求不同外,還有「儲存差異」:
SVN關心:檔案內容的具體差異;而Git關心:檔案整體是否發生改變。 SVN每次提交記錄的是:「哪些檔案進行了修改,修改了哪些行的哪些內容」。
如圖,Version 2中記錄的是檔案A和C的變化,而Version 3中記錄檔案C的變化,以此類推; 而Git中,並不儲存這些前後變換的差異資料,而是儲存整個快取區中的所有檔案, 又稱快照,「有變化的檔案儲存,沒變化的檔案不儲存,而是對上次儲存的快照做一個連結」, 因為這種不同的儲存方式,使得Git切換分支的速度比SVN快上不少。
當然SVN也有它的優點,比如「許可權控制」,可以設定每個賬戶的讀寫許可權,而Git中 則沒有響應的許可權控制。至於用哪個的,還是看公司要求吧~
3. Git的四個組成部分
簡單說下Git的四個組成部分:
- 工作區:不包含.git資料夾在內的整個專案目錄,所有修改都在工作區內進行。
- 暫存區:又稱索引區,本地檔案修改後,執行add操作會把工作區的修改新增到快取區。
- 本地倉庫:當執行commit操作時,暫存區的資料被記錄到本地倉庫中。
- 遠端倉庫:託管專案程式碼的伺服器,多人協作時通過遠端倉庫進行程式碼同合併與同步。
接下來說下這幾個部分是如何協同工作的:
工作區與暫存區:工作區更改,通過git add命令可以把更改提交到暫存區; 也可以git checkout命令使用暫存區內容覆蓋當前的工作區的內容。
暫存區與本地倉庫:可以通過git commit命令把暫存區的內容提交到本地倉庫, 每次commit都會生成一個快照,快照使用Hash值編號。可以通過git reset Hash值, 把某個快照還原到暫存區中。
工作區和本地倉庫:通過git checkout 快照編號,直接把某個快照還原到工作區中。
本地倉庫和遠端倉庫:可以通過git push命令把commit推送到遠端倉庫,多人協作的 時候可能還需要進行一些衝突處理;還有通過git clone拉取某個遠端倉庫的專案到本地, 或通過git fetch拉取遠端倉庫的最新內容,檢查後決定是否合併到本地倉庫中。
工作區和遠端倉庫:這裡兩者的協作一般是git pull,即把遠端主機的最新內容拉取下來後直接合並。
4. Git中檔案的幾個狀態
按照大類劃分,可以分為兩種狀態:Tracked(已跟蹤)和Untracked(未跟蹤), 依據是:「該檔案是否已加入版本控制」?
檔案狀態變化週期流程圖:
流程簡述:
假設某個專案已加入Git版本控制系統
- 1.新建一個檔案,該檔案處於
Untracked
狀態; - 2.通過git add命令新增到快取區,此時檔案處於**
Tracked
狀態又或者說 此時這個檔案已經被版本控制系統所跟蹤,而且他處於Staged
**(暫存)狀態; - 3.通過git commit命令把暫存區的檔案提交提交到本地倉庫,此時檔案處於
Unmodified
(未修改)狀態; - 4.此時如果去編輯這個檔案,檔案又會變成**
Modified
**(修改)狀態;
5. Git中的四類物件
在Git系統中有四種類型的物件,幾乎所有的Git操作都是在這四種物件上進行的,依次為: Blob
(塊)物件,Tree
(樹)物件,Commit
(提交)物件,Tag
(標籤)物件。 前三者的關係如圖所示:
接著我們來詳解的講解這四類物件:
① 塊物件(Blob)
一塊二進位制資料,「僅存放檔案內容」,不包括檔名、許可權等資訊。Git會根據檔案內容計算 出一個Hash值,以這個Hash值作為檔案索引儲存起來。意味著,相同檔案內容的檔案,只會儲存 一個,即共享同一個Blob物件。可以使用:
git hash-object 檔名
來計算檔案內容的Hash值。 如果你知道已經新增到Git中的某個檔案的hash值,還可以通過git cat-file hash值
來讀取資料 物件,可選引數:-p
(檢視Git物件內容) ,-t
(檢視Git物件型別),示例如下:
② 樹物件(Tree)
儲存一個或多個塊物件的引用,每次commit對應一個樹物件,這裡生成一個commit, 然後呼叫**
git ls-tree Hash值
** 檢視樹物件的內容:
利用上面的 git cat-file -p hash值
來檢視blob塊的具體內容:
除了儲存塊物件的引用外,樹物件還可以引用「其他樹物件」,從而構成一個「目錄層次結構」。 新建一個test目錄,複製一個1.txt檔案到這個路徑下,提交一個commit,然後檢視樹物件的內容:
可以指向了另一個tree物件,這個tree物件指向另一個1.txt檔案,樹物件解決了檔名的問題。 而對於提交的人、時間、說明資訊等,我們還需要通過提交物件進行了解。
③ 提交物件(Commit)
儲存樹物件的Hash值,父Commit的Hash值,提交作者、時間、說明資訊。 同樣可以使用**
git cat-file
**命令檢視commit物件:
④ 標籤物件(Tag)
一般會對某次重要的commit加TAG,以示重要,分為兩種情況:
- 輕量級標籤:不會建立真正的TAG物件,而是直接引用commit物件的Hash值。
- 附加標籤:會建立TAG物件,TAG物件中包含commit物件的引用,除此之外會建立 一個檔案:
.git/refs/tags/標籤名
,裡面儲存TAG物件的引用。
這裡為我們上面的兩個commit一次打上兩種標籤,然後看下具體的結果:
Git下載安裝配置
- Windows系統:到 Git For Windows 或 git-for-windows.github.io下載,傻瓜式下一步。
- Linux系統:到 Download for Linux and Unix 下載,如果是Ubuntu的話,直接Terminal鍵入:
sudo apt-get install git
安裝即可。 - Mac系統:到 Installing on Mac 下載,不過新系統貌似預設已經帶有Git了,另外如果安裝了 Homebrew的話可以直接命令列鍵入:
brew install git
進行安裝。
Git本地基本操作
1. 相關配置「git config」
安裝完後,使用Git還需要進行環境的配置,配置資訊儲存在gitconfig檔案中,有三種級別:
- system(系統):系統中所有使用者都會生效,配置檔案:
C:\Program Files\Git\mingw64\etc\gitconfig
, 不同的系統可能不一樣,你可以通過:git config -e --system
,底部可以找到配置檔案的路徑: - global(全域性):當前系統使用者下生效,配置檔案:
C:/Users/當前使用者/.gitconfig
, 同樣可以採用上面的:git config -e --global
檢視配置檔案的位置。 - local(本地):配置僅在當前專案生效,配置檔案:
專案路徑/.git/config
配置生效優先順序:local > global > system,常用命令:
# 配置
git config --global user.name "使用者名稱" # 配置使用者名稱
git config --global user.email "使用者郵箱" # 配置郵箱
git config --global core.editor 編輯器 # 配置編輯器,模式使用vi或者vim
# 檢視配置
git config --global user.name # 檢視配置的使用者名稱
git config --global user.email # 檢視配置的郵箱
# 檢視所有配置列表
git config --global --list # 檢視全域性設定相關引數列表
git config --local --list # 檢視本地設定相關引數列表
git config --system --list # 檢視系統配置引數列表
git config --list # 檢視所有Git的配置(全域性+本地+系統)
複製程式碼
除了命令列的方式外,你還可以直接去編輯對應的配置檔案。
2. 獲取幫助「git help」
git help 命令 # 檢視某個git命令的介紹,用法
git 命令 --help # 另一種寫法
複製程式碼
3. 建立本地倉庫「git init」
git init 倉庫名 # 建立一個新的帶Git倉庫的專案
git init # 為已存在的專案生成一個Git倉庫
複製程式碼
4. 新增檔案到暫存區「git add」
git add 檔名 # 將工作區的某個檔案新增到暫存區。
git add -u # 新增所有被tracked檔案中被修改或刪除的檔案資訊到暫存區,不處理untracked的檔案
git add -A # 新增所有被tracked檔案中被修改或刪除的檔案資訊到暫存區,包括untracked的檔案
git add . # 將當前工作區的所有檔案都加入暫存區
git add -i # 進入互動介面模式,按需新增檔案到快取區
複製程式碼
很多人應該沒用過互動介面模式,這裡演示下用法:
流程簡述:
- 在GitTest資料夾中新建兩個檔案;
- 鍵入git add -i,進入互動介面模式,鍵入4,選擇新增untracked(未標記)的檔案;
- 根據未標記檔案的序號來新增檔案,輸入?會彈出相關提示,直接回車,結束選擇;
- 鍵入4,可以看到已經不存在untracked的檔案了。
5. 讓Git不Tracked特定檔案「.gitignore檔案配置」
當我們使用git add命令把未標記的檔案新增到快取區後,Git就會開始跟蹤這個檔案。 對於一些比如:自動生成的檔案,日誌,臨時編譯檔案,應用簽名檔案等,就沒必要進行跟蹤了, 我們可以編寫一個**「.gitignore檔案」,把不需要跟蹤的檔案和資料夾寫上,git就不會去 跟蹤這些檔案了,另外:.gitignore檔案與.git資料夾在同級目錄下**。
如果不想自己寫這個檔案,可以到 github.com/github/giti… 選擇對應的模板,複製貼上。 也可以自行編寫,支援簡化了的真這個表示式(規範與示例模板摘自:Git王者超神之路)
*
: 匹配零個或多個任意字元[abc]
:只匹配括號內中的任意一個字元[0-9]
:- 代表範圍,匹配0-9之間的任何字元?
:匹配任意一個字元**
:匹配任意的中間目錄,例如a/*/z可以匹配:a/z,a/b/z,a/b/c/z等
模板示例:
# 忽略所有以 .c結尾的檔案
*.c
# 但是 stream.c 會被git追蹤
!stream.c
# 只忽略當前資料夾下的TODO檔案, 不包括其他資料夾下的TODO例如: subdir/TODO
/TODO
# 忽略所有在build資料夾下的檔案
build/
# 忽略 doc/notes.txt, 但不包括多層下.txt例如: doc/server/arch.txt
doc/*.txt
# 忽略所有在doc目錄下的.pdf檔案
doc/**/*.pdf
複製程式碼
有一點要特別注意!!!!
配置.gitignore只對那些沒有新增到版本控制系統的檔案生效(未Tracked的檔案)!
舉個簡單的例子:
有A,B兩個檔案,你先把他兩個add了,然後在.gitignore檔案中 配置了不跟蹤這兩個檔案,但是你會發現根本不會生效。
git add A
git add B
# 配置不跟蹤A和B
git add .gitignore
複製程式碼
所以,最好的做法就是在專案剛開始的時候,先新增.gitignore檔案。 當然,即使是發生了,還是有解決方法的,可以鍵入下述命令清除標 記狀態,然後先新增.gitignore,再新增檔案即可:
git rm -r --cached . # 清除版本控制標記,.代表所有檔案,也可指定具體檔案
複製程式碼
另外,如果你用的IDEA系列的程式碼編輯器,可以安裝一個「.ignore」的外掛,手動 勾選不需要跟蹤的檔案,直接生成.gitignore檔案。
6. 將暫存區的內容提交到本地倉庫「git commit」
git commit -m "提交說明" # 將暫存區內容提交到本地倉庫
git commit -a -m "提交說明" # 跳過快取區操作,直接把工作區內容提交到本地倉庫
複製程式碼
如果不加-m “提交說明”,git會讓用你讓預設編輯器(vi或vim)來編寫提交說明。 除此之外,有時可能想修改上次提交的內容:提交說明,修改檔案等:
# 合併暫存區和最近的一次commit,生成新的commit並替換掉老的。如果快取區沒內容,
# 利用amend可以修改上次commit的提交說明。
#
# 注:因為amend後生成的commit是一個全新的commit,舊的會被刪除,所以別在公共的
# commit上使用amend!切記!!!
git commit --amend
git commit --amend --no-edit # 沿用上次commit的提交說明
複製程式碼
7. 檢視工作區與快取區的狀態「git status」
git status # 檢視工作區與暫存區的當前情況
git status -s # 讓結果以更簡短的形式輸出
複製程式碼
8. 差異對比(內容變化)「git diff」
git diff # 工作區與快取區的差異
git diff 分支名 # 工作區與某分支的差異,遠端分支這樣寫:remotes/origin/分支名
git diff HEAD # 工作區與HEAD指標指向的內容差異
git diff 提交id 檔案路徑 # 工作區某檔案當前版本與歷史版本的差異
git diff --stage # 工作區檔案與上次提交的差異(1.6 版本前用 --cached)
git diff 版本TAG # 檢視從某個版本後都改動內容
git diff 分支A 分支B # 比較從分支A和分支B的差異(也支援比較兩個TAG)
git diff 分支A...分支B # 比較兩分支在分開後各自的改動
# 注:如果只想統計哪些檔案被改動,多少行被改動,可以新增--stat引數
複製程式碼
9. 檢視歷史提交記錄「git log」
git log # 檢視所有commit記錄(SHA-A校驗和,作者名稱,郵箱,提交時間,提交說明)
git log -p -次數 # 檢視最近多少次的提交記錄
git log --stat # 簡略顯示每次提交的內容更改
git log --name-only # 僅顯示已修改的檔案清單
git log --name-status # 顯示新增,修改,刪除的檔案清單
git log --oneline # 讓提交記錄以精簡的一行輸出
git log –graph –all --online # 圖形展示分支的合併歷史
git log --author=作者 # 查詢作者的提交記錄(和grep同時使用要加一個--all--match引數)
git log --grep=過濾資訊 # 列出提交資訊中包含過濾資訊的提交記錄
git log -S查詢內容 # 和--grep類似,S和查詢內容間沒有空格
git log fileName # 檢視某檔案的修改記錄,找背鍋專用
複製程式碼
除此之外,還可以通過 –pretty 對提交資訊進行定製,比如:
更多規則與定製如下(更多可參見:Viewing the Commit History) format對應的常用佔位符:(注:作者是指最後一次修改檔案的人,提交者是提交該檔案的人)
佔位符 | 說明 | 佔位符 | 說明 |
---|---|---|---|
%H |
提交物件(commit)的完整雜湊字串 | %h |
提交物件的簡短雜湊字串 |
%T |
樹物件(tree)的完整雜湊字串 | %t |
樹物件的簡短雜湊字串 |
%P |
父物件(parent)的完整雜湊字串 | %p |
父物件的簡短雜湊字串 |
%an |
作者(author)的名字 | %ae |
作者的電子郵件地址 |
%ad |
作者修訂日期(可以用 –date= 選項定製格式) | %ar |
按多久以前的方式顯示 |
%cn |
提交者(committer)的名字 | %ce |
提交者的電子郵件地址 |
%cd |
提交日期 | %cr |
提交日期,按多久以前的方式顯示 |
%s |
提交說明 |
一些其他操作:
選項 | 說明 |
---|---|
-p |
按補丁格式顯示每個更新之間的差異 |
–stat |
顯示每次更新的檔案修改統計資訊(行數) |
–shortstat |
只顯示 –stat 中最後的行數修改新增移除統計 |
–name-only |
僅在提交資訊後顯示已修改的檔案清單 |
–name-status |
顯示新增、修改、刪除的檔案清單 |
–abbrev-commit |
僅顯示 SHA-1 的前幾個字元,而非所有的 40 個字元 |
–relative-date |
使用較短的相對時間顯示(比如,“2 weeks ago”) |
–graph |
顯示 ASCII 圖形表示的分支合併歷史 |
–pretty |
格式定製,可選選項有:oneline,short,full,Fullerton和format(後跟指定格式) |
還有一些限制log輸出的選項:
選項 | 說明 |
---|---|
-(n) |
僅顯示最近的 n 條提交 |
–since, –after |
僅顯示指定時間之後的提交。 |
–until, –before |
僅顯示指定時間之前的提交。 |
–author |
僅顯示指定作者相關的提交。 |
–committer |
僅顯示指定提交者相關的提交。 |
–grep |
僅顯示含指定關鍵字的提交 |
-S |
僅顯示新增或移除了某個關鍵字的提交 |
10. 檢視某個檔案是誰改動的「git blame」
git blame 檔名 # 檢視某檔案的每一行內容的作者,最新commit和提交時間
複製程式碼
這裡為了演示,先修改一波作者使用者名稱和郵箱,然後往1.txt中新增內容:
Tip:如果你用的IDEA系列的編譯器,右鍵行號,選擇Annotate也可以實現同樣的效果。如:
11. 設定Git命令別名「git config –global alias」
在終端使用Git命令的時候,雖然可以通過按兩次tab來自動補全。但是有些命令比較常用, 每次都要敲完就顯得有些繁瑣了,可以為這些命令起一個簡單的別名,比如: status為st,checkout為co ; commit為ci ; branch為br等,設定示例如下:
git config --global alias.st status
複製程式碼
別名的設定儲存在git的配置檔案中:
12. 為重要的提交打標籤「git tag」
對於某些提交,我們可以為它打上Tag,表示這次提交很重要, 比如為一些正式釋出大版本的 commit,打上TAG,當某個版本出問題了,通過TAG可以快速找到此次提交對應的Hash值, 直接切換到此次版本的程式碼去查詢問題,比起一個個commit找省事多了。
Git中的標籤分為兩種:輕量級標籤 和 附加標籤,命令如下:
git tag 標記內容 # 輕量級標籤
git tag -a 標記內容 -m "附加資訊" # 附加標籤
複製程式碼
如果想為之前某次commit打TAG,可以找出此次提交的Hash值,新增-a選項,示例如下:
git tag -a 標記內容 版本id # 比如:git tag -a v1.1 bcfed96
複製程式碼
另外,git push 的時候預設不會把標籤推送到遠端倉庫,如果想把標籤頁推送到遠端倉庫,可以:
git push origin 標記內容 # 推送某標籤到遠端倉庫
git push origin --tags # 刪除所有本地倉庫中不存在的TAG
複製程式碼
除此之外還有下述常規操作:
git checkout -b 分支名 標記內容 # 新建分支的時候打上TAG
git show 標記內容 # 檢視標籤對應的資訊
git tag -d 標記內容 # 刪除本地TAG
git push origin --delete tag 標記內容 # 刪除遠端TAG
複製程式碼
Git檔案恢復與版本回退
1. 檔案恢復(未commit)「git checkout」
如果在工作區直接刪除已經被Git Tracked的檔案,暫存區中還會存在此檔案:
Git告訴你,工作區的檔案被刪除了,你有兩種可選操作:「刪除快取區檔案」 或 「恢復被刪檔案」:
# 刪除暫存區中的檔案:
git rm 檔名
git commit -m "提交說明"
# 誤刪恢復檔案(用暫存區的檔案覆蓋工作區的檔案)
git checkout -- 檔名
# Tip:git rm 等價於 git rm --cached 檔名 + rm 檔名
# 務必注意:git checkout會拋棄當前工作區的更改!!!不可恢復!!!務必小心!!!
複製程式碼
2. 檔案恢復(已add未commit)「git reset HEAD」
如果更改已經add到暫存區中,想恢復原狀,可以執行下述命令:
git reset HEAD 檔名
git checkout 檔名
複製程式碼
3. 版本回退(已commit)【git reset –hard】
檔案已經commit,想恢復未上次commit的版本或者上上次,可以:
git reset HEAD^ # 恢復成上次提交的版本
git reset HEAD^^ # 恢復成上上次提交的版本,就是多個^,以此類推或用
git reset HEAD~3 # 也可以直接~次數
git reset --hard 版本號 # git log檢視到的Hash值,取前七位即可,根據版本號回退
複製程式碼
reset命令的作用其實就是:重置HEAD指標,讓其指向另一個commit,而這個動作可能會對 快取區造成影響,舉個例子:
本來的分支線:- A - B - C (HEAD, master),git reset B後:- A - B (HEAD, master) 解釋:看不到C了,但是他還是存在的,可以通過git reset C版本號找回,前提是 C沒有被Git當做垃圾處理掉(一般是30天)。
reset提供了三個可選引數:
- soft:只是改變HEAD指標指向,快取區和工作區不變;
- mixed:修改HEAD指標指向,暫存區內容丟失,工作區不變;
- hard:修改HEAD指標指向,暫存區內容丟失,工作區恢復以前狀態;
4. 檢視輸入過的指令記錄「git reflog」
Git會記住你輸入的每個Git指令,比如上面的git reset 切換成一箇舊的commit,然後 git log後發現新提交的記錄沒了,想切換回新的那次commit, 可以先調git reflog 獲取新commit的Hash值,然後git reset 回去。
git reflog
複製程式碼
注:指令記錄不會永久儲存!Git會定時清理用不到的物件!!!
5. 撤銷某次提交「git revert」
有時可能我們想撤銷某次提交所做的更改,可以使用revert命令
git revert HEAD # 撤銷最近的一個提交
git revert 提交的Hash值 # 撤銷某次commit
複製程式碼
注意!!!
不是真的把提交給撤銷了,而是生成一個新的提交來覆蓋舊的提交,被撤銷的提交 和新的提交記錄都會儲存!!!如果不信的話,你可以再鍵入git revert HEAD, 會發現被撤銷的更改又變回來了。簡單點說:「撤銷的只是檔案變化,提交記錄依舊存在」。
6. 檢視某次提交的修改內容「git show」
git show 提交Hash值 # 檢視某次commit的修改內容
複製程式碼
7. 檢視分支最新commit的Hash值「git rev-parse」
git rev-parse 分支名 # 檢視分支最新commit的Hash值,也可以直接寫HEAD
複製程式碼
8. 找回丟失物件的最後一點希望「git fsck」
因為你的某次誤操作導致commit丟失,如果git reflog都找不到,你可以使用git fsck,找到丟失 的物件的版本Hash值,然後恢復即可。
git fsck --lost-found
複製程式碼
Git本地分支
1.分支的概念
分支並不是Git物件,和輕量級的TAG物件類似,只包含對commit物件的索引。只是分支更新後, 索引會替換為最新的commit,而TAG物件建立後索引就不在變化。分支檔案儲存與下述兩個路徑:
- 本地分支:
當前專案/.git/refs/heads/
- 遠端分支:
當前專案/.git/refs/remotes/
說到分支,必然會提及HEAD,它指向「當前工作的本地分支」,對應檔案:當前專案/.git/HEAD
下面通過示例和圖解的方式幫大家理解分支:
如法炮製,提交兩次:
從上面的圖中不難發現這樣的規律:每次commit,master都會向前移動,指向最新提交。 這個時候可能有些童鞋會問:commit之間的箭頭哪來的?或者說commit怎麼串成一條線的?
答:還記得一開始介紹的commit物件嗎?裡面有一個parent的值,指向父commit的Hash值。
2.建立其他分支的原因
通過兩個常見的場景來體會建立其他分支的必要性:
- 場景一:
專案一般都是一步步迭代升級的,有大版本和小版本的更新: 大版本一般是改頭換面的更新,比如 UI大改,架構大改,版本是: v2.0.0這樣;小版本的更新一般是UI小改,Bug修復優化等,版本是: v2.0.11這樣;只有一條master分支,意味著:你的分支線會 非常非常的長,假如你已經發布到了 第二個大版本,然後使用者反饋第一個版本有很嚴重的BUG,這時候想切回第一個版本改BUG, 然後改完BUG切回第二個大版本,想想也是夠嗆的。 (PS:可能你說我可以對重要的commit打tag, 然後找到這個tag 切回去,當然也行這裡是想告訴你引入其他分支會給你帶來的便利)
- 場景二:
如果只有一個master分支的話,假如某次commit衝突了,而這個衝突很難解決或者解決不了, 那麼整個開發就卡在這裡,無法繼續向後進行了。
3.一個簡單的分支管理策略
為了解決只有一個master分支引起的問題,可以引入分支管理,最簡單的一種策略如下:
在master分支上開闢一個新的develop分支,然後我們根據功能或者業務,再在develop 分支上另外開闢其他分支,完成分支上的任務後,再將這個分支合併到develop分支上! 然後這個功能分支的任務也到此結束,可以刪掉,而當釋出正式版後,再把develop分支 合併到master分支上,並打上TAG。
master與develop分支都作為長期分支,而其他建立的分支作為臨時性分支! 簡述各個分支的劃分:
- master分支:可直接用於產品釋出的程式碼,就是正式版的程式碼
- develop分支:日常開發用的分支,團隊中的人都在這個分支上進行開發
- 臨時性分支:根據特定目的開闢的分支,包括功能(feature)分支,或者預釋出(release)分支, 又或者是修復bug (fixbug)分支,當完成目的後,把該分支合併到develop分支, 然後刪除該分支,使得倉庫中的常用分支始終只有:master和develop兩個長期分支!
4.分支建立與切換「git branch」
git branch 分支名 # 建立分支
git branch # 檢視本地分支
複製程式碼
我們在master分支上建立一個develop分支,此時的版本線變成了這樣:
此時雖然已經建立了develop分支,但是HEAD還是指向master,接著我們來切換分支:
git checkout 分支名 # 切換分支
git checkout -b 分支名 # 建立分支同時切換到這個分支
複製程式碼
切換到develop後,提交一次,此時的版本線:
再提交一次,然後切換為master分支,此時的版本線:
切換回master後,提交一次,此時的版本線:
行吧,講到這裡,相信各位童鞋對Git中的分支已經有所瞭解了。
5.分支合併「git merge」 VS 「git rebase」
Git中,可以使用「git merge」和「git rebase」兩個命令來進行分支的合併。
git merge合併分支
合併的方式分為兩種:快速合併 和 普通合併,兩者的區別在於: 「前者合併後看不出曾經做過合併,而後合併後的歷史會有分支記錄」 如圖所示:
快速合併,預設,快速合併有一個前提:「當前分支的每個提交都在另一個分支中」, Git不建立任何新的commit,只是將當前分支指向合併進來的分支。下面演示下快速合併, 執行git reset 切換到第四次commit,然後執行git merge develop合併master分支。
普通合併,新增**–no-ff**引數表示禁用快速合併。
另外有時會有這樣的場景:合併的分支中有很多commit記錄是無需在分支中體現的,一個commit 就夠了。可以藉助**--squash**引數來壓縮提交,示例如下:
附:git merge的常用引數:
git merge -ff # 快速合併,預設引數
git merge -ff-only # 只有快速合併的情況才合併
git merge --no-ff # 不使用快速合併
git merge -n 分支名 # 合併分支,不會在合併後顯示合併前後的不同狀態
git merge -stat 分支名 # 合併分支,合併結束後顯示合併前後的不同狀態
git merge -e 分支名 # 合併分支,合併前呼叫編輯器,可自行編寫commit
複製程式碼
Tips: git-merge除了用來合併分支外,拉取遠端倉庫更新時也可用到(git fetch + git merge)
git reabse合併分支
rebase(衍合,變基),網上很多教程寫得很高深莫測,其實並沒有那麼複雜, 只是這種合併會讓樹整潔,易於跟蹤。以上面4中的結果為例,先把master分支 和develop分支重置到最新的commit。
先走一波前面的merge合併方式:
接著再試試rebase合併方式:
Git會把每個提交都取消掉,並把他們臨時儲存為補丁,比如經過一些衝突解決,生成新的commit, 舊的commit會被丟棄,還會被git的gc回收,這樣的結果就是一條直線的樹。
6.解決合併衝突
在分支的合併的時候,並不是每次都能直接合並的,有時會遇到合併衝突,特別是在多人協作的時候。 出現合併衝突後,需要解決完衝突,才能繼續合併。
舉個簡單的例子,A和B在master分支上開闢出兩個分支來完成相關的功能, A做完了,把自己的分支合併到master分支,此時master分支向前移動了幾次commit, 接著B也完成了他的功能,想把自己分支合併到master分支,如果改動的檔案和和A改動 的檔案相同的話,此時就會合並失敗,然後需要處理完衝突,才能夠繼續合併!
接下來我們來簡單的模擬合併衝突,先來試試merge:
merge分支後處理衝突
如圖,合併完A分支後合併B分支出現了衝突,接著鍵入:git status檢視衝突的檔案:
可以看到未合併的兩個檔案,1.txt和2.txt,開啟其中一個檔案:
<<< 和 >>>包裹著的就是衝突內容,保留自己想要的內容,處理完後刪掉<<<和>>>,修改完後:
2.txt檔案也如法炮製,接著add,然後commit即可,合併結束。
rebase分支後處理衝突
如圖,A合併成功,在合併B的時候,出現了合併衝突,有三個可選的操作:
git rebase --continue # 處理完衝突後,繼續處理下一個補丁
git rebase --abort # 放棄所有的衝突處理,恢復rebase前的情況
git rebase --skip # 跳過當前的補丁,處理下一個補丁,不建議使用,補丁部分的commit會丟失!
複製程式碼
鍵入git status檢視衝突檔案:
接著處理1.txt檔案中的衝突,解決完成後,先鍵入git add,接著鍵入git rebase --continue 處理下一個衝突:
處理接下來的衝突,直到沒有衝突為止:
可以看到使用rebase合併,最後的分支線是一條直線。另