《吐血整理》一篇文章教你學廢Git版本管理

>
本文內容簡述
Git概述 ① 什麼是版本管理系統 ② Git和SVN的區別
③ Git的四個組成部分 ④ Git中文件的幾個狀態
⑤ Git中的四類對象
Git下載安裝配置
Git本地基本操作 ① 配置「git config ② 獲取幫助「git help
③ 創建Git倉庫「git init
④ 添加文件到暫存區「git add
⑤ 讓Git不跟蹤特定文件「.gitignore文件
⑥ 暫存區內容提交到本地倉庫「git commit
⑦ 查看工作區與暫存區狀態「git status
⑧ 內容變化,差異對比「git diff
⑨ 查看歷史提交記錄「git log
⑩ 查看某個文件的改動記錄「git blame
⑪ 設置Git命令別名「git config --global alias
⑫ 爲重要提交打標籤「git tag
Git文件恢復與版本回退 ① 文件恢復,未add「git checkout
② 文件恢復,已add未commit「git reset HEAD
③ 版本回退,已commit「git reset --hard
④ 查看輸入過的指令記錄「git reflog
⑤ 撤銷某次提交「git revert
⑥ 查看某次提交的修改內容「git show
⑦ 查看分支最新commit的Hash值「git rev-parse
⑧ 找回丟失對象的最後一點希望「git fsck
Git本地分支 ① 分支的概念 ② 創建其他分支的原因
③ 一個簡單的分支管理策略
④ 分支的創建於切換「git branch
⑤ 分支合併「git merge」 VS 「git rebase
⑥ 解決合併衝突 ⑦ 刪除分支
⑧ 恢復誤刪分支「git log --branches ⑨ 分支重命名
⑩ 切換分支時暫存未commit的更改「git stash
⑪ 把commit從一個分支挪到另一個分支「git cherry-pick
Git遠程倉庫 ① 遠程倉庫概述
② 本地倉庫與遠程倉庫建立關聯「git remote
③ 推送本地倉庫到遠程倉庫「git push
④ 克隆遠程倉庫「git clone
⑤ 同步遠程倉庫更新「git fetch」VS「git pull
⑥ git push 時的unrelated history問題
⑦ SSH Key避免每次push重複輸入賬號密碼
Git工作流 ① 集中式工作流 ② 功能分支工作流
③ Gitflow工作流 ④ Forking工作流
⑤ Pull Request工作流
其他雜項 ① 爲開源項目貢獻代碼 ② SourceTree使用詳解

0x1、Git概述——分佈式版本控制系統


瞭解Git相關的概念,有助於後續命令的掌握~


1. 什麼是版本管理系統

VCS(Version Control System),一種用於記錄一個或多個文件內容變化
歷史,以便將來能對特定版本的歷史記錄進行查看,更改,備份還原的系統。
可以簡單類比爲「遊戲存檔」,打Boss前存下檔,沒過關,重新讀檔;
分支劇情,想體驗不同選擇觸發的不同劇情,可以存多個檔, 想玩哪個讀哪個。

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一次打上兩種標籤,然後看下具體的結果:


0x2、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 進行安裝。

0x3、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文件夾在同級目錄下**。

如果不想自己寫這個文件,可以到 https://github.com/github/gitignore 選擇對應的模板,複製粘貼。
也可以自行編寫,支持簡化了的真這個表達式(規範與示例模板摘自: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


0x4、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 -> C
feature分支:a -> b

現在想把feature分支上的b,放到master的後,可以這樣操作:

  • Step 1:切換到feature分支上,git log拿到b commit的版本號(SHA1)。
  • Step 2:切換到master分支,鍵入:git cherry-pick 版本號。


0x5、Git遠程倉庫


1.遠程倉庫概述

在實際開發過程中,基本都是團隊協作的形式進行,即多人一起負責同一個項目,那如何共享同一份代碼並進行管理呢?可以用到「Git遠程倉庫」。可以自己搭建,或選擇專業的代碼託管平臺,比如:Github,Git@OSC,GitCafe,GitLab,coding.net,gitc,BitBucket,Geakit,Douban CODE 等。當然,如果有條件的話,肯定是自己搭建的爽一些,可控,還可以做一些訂製(集成編譯,機器人提醒等),簡單點的可以試試「Gogs」,可玩性更高的可以試試「GitLab」。

2.本地倉庫與遠程倉庫建立關聯「git remote」

在Github上新建了一個項目倉庫,會生成對應的倉庫鏈接,如:

鍵入下述命令進行關聯:

git remote add origin 遠程倉庫地址

接着可鍵入下述命令查看關聯情況:

git remote      # 列出已經存在的遠程分支
git remote -v   # 查看遠程倉庫的地址

3.推送本地倉庫到遠程倉庫「git push」

建立完關聯後,我們可以使用git push命令把本地更改推送到遠程倉庫

git push -u origin master

-u參數:作爲第一次提交使用,作用是把本地master分支和遠程master分支關聯起來(設置默認遠程主機),後續提交不需要這個參數!

另外,如果想修改遠程倉庫地址,可通過下述命令:

# 直接修改遠程倉庫地址
git remote set-url origin 遠程倉庫地址

# 也可以先刪除origin後再添加
git remote rm origin               # 刪除倉庫關聯
git remote add origin 遠程倉庫地址   # 添加倉庫關聯

你還可以直接修改「.git文件夾中的config文件」,直接替換圈住位置內容即可:

還有一點:「origin」並不是固定的東西,只是後面「倉庫地址的一個別名」!!可以寫成其他的東西,然後你也可以設置多個倉庫關聯,用不同的別名標誌,比如:

git remote add github https://github.com/coder-pig/SimpleTea.git
git remote add osc [email protected]:coder-pig/SimpleTea.git

4.克隆遠程倉庫「git clone」

把項目推送到遠程倉庫後,其他開發者就可以通過git clone命令把項目克隆到本地

git clone 倉庫地址          # 克隆項目到當前文件夾下
git clone 倉庫地址 目錄名    # 克隆項目到特定目錄下

# 注:git clone命令只會建立master分支,如果想克隆特定遠程分支,可在克隆後:
git checkout -t origin/dev 

# 該命令等同於
git checkout -b dev origin/dev

# 除此之外,還可以:
git fetch origin 遠程分支:本地分支 # 會在本地新建分支,但不會自動切換,還需checkout
git branch --set-upstream 本地分支 遠程分支 # 建立本地分支與遠程分支的鏈接

5.同步遠程倉庫更新「git fetch」VS 「git pull」

獲取遠程倉庫更新的方法有兩種:fetchpull,簡要講解下兩者的區別:

  • git fetch

僅僅只是從遠處服務器獲取到最新版本到本地,假如你不去合併(merge),本地工作空間是不會發生變化的!比如:在Github上創建一個README.md文件,然後調 git fetch 去獲取遠程倉庫的更新。

  • git pull

一步到位,pull = fetch + merge,比如:同樣修改Github上的README.md 文件,然後git pull 同步遠程倉庫的更新:

區別顯而易見,使用git fetch會更安全一些,畢竟merge的時候,查看更新的情況,再決定是否進行合併。

6.git push 時的unrelated history問題

在Github創建新項目後,在repo處創建了README.md或其他文件,然後關聯本地倉庫,push時會報錯:
Push rejected: Push to origin/master was rejected,然後提示你pull一下,當你pull時又會報錯:
refusing to merge unrelated histories」,原因是兩個倉庫不同導致的,可使用下述命令解決:

git pull origin master --allow-unrelated-histories

除此之外,你還可以粗暴一點,直接用本地倉庫「強制覆蓋遠程倉庫」,但是 慎用!!!如果出問題了,只能看下其他人的電腦中是否有原始的本地倉庫進行還原!!!

git push -f origin  # 慎用!!!

7.SSH Key避免每次push重複輸入賬號密碼

私有項目,使用Https協議pull或push,都需要驗證賬號和密碼,有點繁瑣,如果想避免這種重複輸入的情況,可以考慮使用SSH協議。SSH,Secureshell(安全外殼協議),專爲遠程登陸會話與其他網絡服務提供安全性的協議,而SSH傳輸的數據是可以經過壓縮的,可以加快傳輸的速度,出於安全性與速度,優先考慮使用SSH協議,而SSH的安全驗證規則又分爲基於密碼和基於密鑰兩種!這裏使用的是第二種,即在本地創建一對密鑰「公鑰(id_rsa.pub)和私鑰(id_rsa)」然後把公鑰內容貼到遠程倉庫設置中的ssh keys中,從而建立本地與遠程的認證關係。配置SSH Key的流程如下:

  • ① 來到電腦的根目錄下(假設還沒創建過SSH key):

執行完ssh-keygen那個指令後,後面依次要你輸入文件名
直接回車 → 會生成兩個默認的祕鑰文件,接着提示輸入密碼,
直接回車 → 如果這裏你輸入密碼了的話,那麼push的時候你還是需要輸入密碼,接着又輸多一次密碼
直接回車 → 出現最下面的這串東西就說明ssh key已經創建成功了!
接着可以用編輯器打開id_rsa.pub文件或者鍵入下述命令複製內容:

clip <id_rsa.pub

打開Github,點擊頭像,選擇:Settings,然後點擊左側SSH Keys,然後New SSH Key

然後Github會給你發來一個提示創建了一個新ssh key的郵件,無視就好,接下來我們可以鍵入:

**ssh -T [email protected]**

然後如果上面設置過密碼則需要輸入密碼,否則直接輸入yes然後一直按回車就好!,最後出現Hi xxx那句話就說明ssh key配置成功了!

其他遠程倉庫配置方法類似,另外如果想一個電腦管理多個SSH-Key,可移步至:

《Git拾遺:一機多SSH-Key管理》


0x6、Git工作流


關於Git工作流,Github上有一篇圖文並茂寫得很好的文章,就不細說了,只是簡單介紹下,更多詳情可見:《Git Workflows and Tutorials》

1.集中式工作流

類似於SVN,不過只有一條master分支,然後一羣人就在這條分支上嗨,比如有小A和小B:

  • 1.項目管理者初始化倉庫,然後推到遠程倉庫
  • 2.其他人克隆遠程倉庫項目到本地
  • 3.小A和小B完成各自的工作
  • 4.小A先完成了,git push origin master 把代碼推送到遠程倉庫
  • 5.小B後完成了,此時推送代碼到遠程倉庫,出現文件修改衝突
  • 6.小B需要先解決衝突,git pull –rebase origin master,然後rebase慢慢玩
  • 7.小B把衝突解決後,git push origin master 把代碼推送到遠程倉庫

2.功能分支工作流

和集中式分部流相比只是分支再不是隻有master,而是根據功能開闢新的分支而已,示例如下:

  • 1.小A要開發新功能,git branch -b new-feature 開闢新分支
  • 2.小A在new-feature上新功能相關的編寫,他可以這個分支推到遠程倉庫
  • 3.功能完成後,發起請求pull request(合併請求),把new-feature合併到master分支
  • 4.倉庫管理員可以看到小A的更改,可以進行一些評註,讓小A做某些更改,
    然後再發起pull request,或者把pull request拉到本地自行修改。
  • 5.倉庫管理員覺得可以了,合併分支到master上,然後把new-feature分支刪掉

:這裏的倉庫管理者是擁有倉庫管理權限的人

3.Gitflow工作流

其實就是功能分支工作流做了一些規範而已,大概流程參見上面「一個簡單的分支管理策略

4.Forking工作流

分佈式工作流,每個開發者都擁有自己獨立的倉庫,爲開源項目貢獻代碼常用,把項目fork到自己的遠程倉庫,完成相應更改,然後pull request到源倉庫,源倉庫管理者可以決定是否合併。

5.Pull Request工作流

和Forking工作流類似,Pull Requests是Bitbucket上方便開發者之間協作的功能


0x7、其他雜項


1.爲開源項目貢獻代碼

你可以Clone別人的開源項目,在看別人代碼的時候,覺得作者某些地方寫得不好,寫錯,或者你有更好的想法,在本地修改後,想把修改push推送到開源項目上,是無法直接Push推送更改的。參與開源項目的方式有兩種:

  • 方法一
    是讓作者把你加爲寫作者,添加協作者流程:
    點擊倉庫的SettingsCollaborators 然後輸入想添加的人的用戶名或者郵箱,點擊
    添加即可。

  • 方法二
    點擊Fork按鈕,把這個項目fork到自己的賬號下,然後Clone到本地,然後做你想做的修改,commit提交,然後push到自己賬號裏的倉庫,然後打開開源項目,點擊,然後新建一個「pull request」,接着設置自己的倉庫爲源倉庫,設置源分支,目標倉庫與目標分支,然後還有pull request的標題和描述信息,填寫完畢後,確定。
    這個時候開源項目的作者就會收到一個pullrequest的請求,由他來進行審覈,作者審查完代碼覺得沒問題的話,他可以點擊一下merge按鈕即可將這個pull request合併到自己的項目中,假如作者發現了你代碼中還有些bug,他可以通過Pull Request跟你說明,要修復了xxBUG才允許合併,那麼你再修改下BUG,提交,更改後的提交會進入Pull Request,然後作者再審覈這樣!

Tips:o(╯□╰)o假如作者不關閉或者merge你的這個Pull Request,你可以一直commit騷擾主項目…

2.SourceTree使用詳解

命令行雖酷炫可裝逼,但是有時用圖形化工具還是能提高不少效率的,安利個巨好用的Git圖形化工具SourceTree,官網下載地址:https://www.sourcetreeapp.com/,網上教程滿天飛,筆者也不粘貼複製了,找到個寫得還行的,有興趣可移步至:《用SourceTree輕鬆Git項目圖解》


後面有新的會更新,待續…


參考文獻與更多Git學習資料


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章