1. 程式人生 > >今年下半年,中日合拍的《Git遊記》即將正式開機,我將...(上集)

今年下半年,中日合拍的《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 Windowsgit-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合併,最後的分支線是一條直線。另