一回首,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.txtdoc/*.txt# 忽略所有在doc目錄下的.pdf檔案doc/**/*.pdf複製程式碼
有一點要特別注意!!!!
配置.gitignore只對那些沒有新增到版本控制系統的檔案生效(未Tracked的檔案)!
舉個簡單的例子:
有A,B兩個檔案,你先把他兩個add了,然後在.gitignore檔案中配置了不跟蹤這兩個檔案,但是你會發現根本不會生效。
git add Agit add B# 配置不跟蹤A和Bgit 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 分支名 標記內容 # 新建分支的時候打上TAGgit show 標記內容 # 檢視標籤對應的資訊git tag -d 標記內容 # 刪除本地TAGgit 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合併,最後的分支線是一條直線。另外,使用rebase合併中途出差錯,可以使用git rebase –abort恢復rebase前的狀態。
7.刪除分支
合併完的分支,基本沒什麼用了,可以使用下述命令刪除:
git branch -d 分支名 # 刪除分支,分支上有未提交更改是不能刪除的git branch -D 分支名 # 強行刪除分支,儘管這個分支上有未提交的更改複製程式碼
8.恢復誤刪分支
兩步:找出被刪分支最新的commit的Hash值,然後恢復分支:
git log --branches="被刪除的分支名" # 找到被刪分支最新的commit版本號git branch 分支名 版本號(前七位即可) # 恢復被刪分支複製程式碼
9.切換分支時暫存未commit的更改「git stash」
有時我們可能在某個分支上正編寫著程式碼,然後有一些突發的情況,需要 我們暫時切換到其他分支上,比如要緊急修復bug,或者切換分支給同事 review程式碼,此時如果直接切換分支是會提示切換失敗的,因為這個分支 上做的更改還沒有提交,你可以直接add後commit,然後再切換,不過我們習慣寫完某個功能再提交,我們想:
先暫存這個分支上的改動,切去其他分支上搞完事,然後回來繼續繼續在之前的改動上寫程式碼。
那麼可以使用:
git stash # 儲存當前的改動複製程式碼
然後放心的切換分支,然後再切換回來,接著使用:
git stash apply # 恢復儲存改動複製程式碼
另外有一點一定要注意!!!可以stash多個改動!!如果你切換到另一個分支又stash了,然後切換回來stash apply是恢復成另一個分支的stash!!!如果你這樣stash了多次的話,我建議你先鍵入:
git stash list # 檢視stash列表複製程式碼
找到自己想恢復的那個
比如這裡恢復的應該是master上的stash,可以使用下述命令進行恢復:
git stash apply stash@{1
}複製程式碼
10.分支重新命名
git branch -m 老分支名 新分支名 # 分支重新命名複製程式碼
11.把提交的commit從一個分支放到另一個分支「git cherry-pick」
有時我們可能需要把某個分支上的一次commit放到另一個分支上,此時可以使用git cherry-pick,比如下面這樣兩個分支:
master分支:A ->
B ->
Cfeature分支:a ->
b
現在想把feature分支上的b,放到master的後,可以這樣操作:
- Step 1:切換到feature分支上,git log拿到b commit的版本號(SHA1)。
- Step 2:切換到master分支,鍵入:git cherry-pick 版本號。
PS:文章後續還會調整…有疑問的地方歡迎在評論區提出,謝謝~
參考文獻與更多Git學習資料: