天天看點

Git Pro深入淺出(二)

了解和熟悉下面的Git工具,會使你毫無壓力地在指令行中使用Git來完成日常中的大部分事情。

六、Git工具

1. 選擇修訂版本

Git允許通過幾種方法來指明特定的或者一定範圍内的送出。

git show <commitid>
git show <簡短的SHA-1>           

複制

SHA-1 的前幾個字元就可以獲得對應的那次送出,當然你提供的 SHA-1 字元數量不得少于4個,并且沒有歧義——也就是說,目前倉庫中隻有一個對象以這段 SHA-1 開頭。

# --abbrev-commit顯示簡短且唯一的值
$ git log --abbrev-commit --pretty=oneline           

複制

(1)引用日志

Git會在背景儲存一個引用日志(reflog),引用日志記錄了最近幾個月你的 HEAD 和分支引用所指向的曆史。

$ git reflog           

複制

每當HEAD所指向的位置發生了變化,Git就會将這個資訊存儲到引用日志這個曆史記錄裡。

# 顯示制定送出記錄
$ git show HEAD@{21}
# 顯示昨天送出記錄
$ git show master@{yesterday}           

複制

(2)祖先引用

# 檢視上一個送出
$ git show HEAD^
# 檢視d921970的祖父送出
$ git show d921970~2           

複制

(3)送出區間

# 在develop分支中而不在master分支中的送出
$ git log master..develop
# 在master分支中而不在develop分支中的送出
$ git log develop..master
# 在你目前分支中而不在遠端 origin 中的送出
$ git log origin/master..HEAD
# 檢視所有被refA或refB包含的但是不被refC包含的送出
$ git log refA refB ^refC
$ git log refA refB --not refC
# 選擇出被兩個引用中的一個包含但又不被兩者同時包含的送出
$ git log --left-right master...develop           

複制

2. 互動式暫存

當你修改一組檔案後,希望這些改動能放到若幹送出而不是混雜在一起成為一個送出時,互動式暫存變得非常有用。

$ git add -i/--interactive           

複制

Git Pro深入淺出(二)

3. 儲藏與清理

當你在項目的一部分上已經工作一段時間後,所有東西都進入了混亂的狀态,而這時你想要切換到另一個分支做一點别的事情。問題是,你不想僅僅因為過會兒回到這一點而為做了一半的工作建立一次送出。針對這個問題的答案是git stash指令。其會将修改的檔案儲存到一個棧上,而你可以在任何時候重新應用這些改動。

$ git stash
$ git stash save
# 檢視儲藏的東西
$ git stash list           

複制

Git Pro深入淺出(二)
# 重新應用儲藏
$ git stash apply stash@{2}           

複制

注意:

  • 可以在一個分支上儲存一個儲藏,切換到另一個分支,然後嘗試重新應用這些修改
  • 當應用儲藏時工作目錄中也可以有修改與未送出的檔案,如果有任何東西不能幹淨地應用,Git會産生合并沖突。
# 從棧上删除儲藏
$ git stash drop stash@{2}
# 應用後立即删除
$ git stash pop           

複制

(1)創造性的儲藏

不儲藏任何你通過 git add 指令已暫存的東西

$ git stash --keep-index           

複制

git stash

隻會儲藏已經在索引中的檔案。

如果指定

--include-untracked

-u

标記,Git也會儲藏任何建立的未跟蹤檔案。

$ git stash -u           

複制

(2)從儲藏建立一個分支

$ git stash branch <branchname> <stash>           

複制

其建立一個新分支,檢出儲藏工作時所在的送出,重新在那應用工作,然後在應用成功後扔掉儲藏。是在新分支輕松恢複儲藏工作并繼續工作的一個很不錯的途徑。

(3)清理工作目錄

移除工作目錄中所有未追蹤的檔案以及空的子目錄(-f意味着“強制”或“确定移除”)。

$ git clean -f -d           

複制

-n 選項來運作指令,這意味着 “做一次演習然後告訴你 将要 移除什麼”。

$ git clean -d -n           

複制

更安全的方式,将所有東西放到儲藏棧中,同樣達到了清理工作目錄的目的。

git stash --all           

複制

4. 簽署工作

每個人生成私鑰,用生成的密鑰來簽署标簽與送出。

5. 搜尋

(1)浏覽代碼

grep指令,可以很友善地從送出曆史或者工作目錄中查找一個字元串或者正規表達式。

# 搜尋所有檔案中包含“ligang”的檔案,并顯示行号
$ git grep -n ligang
# 輸出概要資訊
$ git grep --count ligang
# 想看比對的行是屬于哪一個方法或者函數
$ git grep -p js-pt-settings-user
$ git grep --break --heading -p js-pt-settings-user           

複制

  • –break:多個檔案之間空行隔開
  • –heading:檔案名獨占一行

(2)日志搜尋

# 想知道是什麼時候存在或者引入的靜态常量"ZLIB_BUF_MAX"
$ git log -SZLIB_BUF_MAX --oneline
$ git log -Sligang --oneline
# 行日志搜尋
# 檢視zlib.c檔案中git_deflate_bound函數的每一次變更
$ git log -L :git_deflate_bound:zlib.c           

複制

5. 重寫曆史

(1)修改最後一次送出

對最近一次送出,修改送出資訊,或者修改你添加、修改和移除的檔案的快照。

$ git commit --amend           

複制

注意:其修正會改變送出的SHA-1校驗和,類似于一個小的變基。如果已經推送了最後一次送出就不要修正它。

(2)修改多個送出資訊

為了修改在送出曆史中較遠的送出,必須使用更複雜的工具。Git沒有一個改變曆史工具,但是可以使用變基工具來變基一系列送出。

# 在HEAD~3..HEAD範圍内的每一個送出都會被重寫,無論你是否修改資訊
$ git rebase -i HEAD~3           

複制

6. 重置揭密

(1)三棵樹

了解reset和checkout的最簡方法,就是以Git的思維架構(将其作為内容管理器)來管理三棵不同的樹。“樹” 在我們這裡的實際意思是“檔案的集合”,而不是指特定的資料結構。

Git 作為一個系統,是以它的一般操作來管理并操縱這三棵樹的:

用途
HEAD 上一次送出的快照,下一次送出的父結點
Index 預期的下一次送出的快照
Working Directory 沙盒

HEAD:HEAD是目前分支引用的指針,它總是指向該分支上的最後一次送出。 這表示 HEAD 将是下一次送出的父結點。 通常,了解 HEAD 的最簡方式,就是将它看做你的上一次送出的快照。

# 顯示了 HEAD 快照實際的目錄清單
$ git cat-file -p HEAD           

複制

Index:索引是你的“預期的下一次送出”–“暫存區域”,運作git add後,代碼就進入“暫存區域”。

# 顯示出索引目前的樣子
$ git ls-files -s           

複制

Working Directory:可以把工作目錄當做“沙盒”。在将修改送出到暫存區并記錄到曆史之前,可以随意更改。

(2)工作流程

Git主要的目的是通過操縱這三棵樹來以更加連續的狀态記錄項目的快照。

Git Pro深入淺出(二)

(3)重置的作用

将目前的分支重設(reset)到指定的

<commit>

或者

HEAD

(預設是HEAD,即最新的一次送出),并且根據[mode]有可能更新index和working directory(預設是mixed)。

$ git reset [--hard|soft|mixed|merge|keep] [commit|HEAD]           

複制

A). –hard:重設index和working directory,從

<commit>

以來在working directory中的任何改變都被丢棄,并把HEAD指向

<commit>

$ git add test1
$ git commit -m"Add test1"
$ git add test2
$ git commit -m"Add test2"
$ git log --oneline           

複制

Git Pro深入淺出(二)
$ git reset --hard HEAD~1           

複制

Git Pro深入淺出(二)
$ git log --oneline           

複制

Git Pro深入淺出(二)
$ git status #沒有任何内容           

複制

說明:第二次送出的test2已被丢棄!HEAD指針重新指向了第一次送出的commitID。徹底回退到某個版本,本地的源碼也會變為上一個版本的内容。

B). –soft:index和working directory中的内容不作任何改變,僅僅把HEAD指向

<commit>

。自從

<commit>

以來的所有改變都會顯示在git status的“Changes to be committed”中。

$ git add test1
$ git commit -m"Add test1"
$ git add test2
$ git commit -m"Add test2"
$ git log --oneline           

複制

Git Pro深入淺出(二)
$ git reset --soft HEAD~1 #不會有任何提示
$ git log --oneline           

複制

Git Pro深入淺出(二)
$ git status           

複制

Git Pro深入淺出(二)

說明:第二次送出的test2被重置到了”Changes to be committed”中!HEAD指針重新指向了第一次送出的commitID。回退到某個版本,隻回退了commit的資訊。如果還要送出,直接commit即可。

C). –mixed:僅重設index,但是不重設working directory。這個模式是預設模式,即當不顯示告知

git reset

模式時,會使用mixed模式。這個模式的效果是,working directory中檔案的修改都會被保留,不會丢棄,但是也不會被标記成“Changes to be committed”,但是會打出什麼還未被更新的報告。

$ git add test1
$ git commit -m"Add test1"
$ git add test2
$ git commit -m"Add test2"
$ git log --oneline           

複制

Git Pro深入淺出(二)
#不會有任何提示(預設方式,可以省略--mixed)#不會有任何提示(預設方式,可以省略--mixed)
$ git reset --mixed HEAD~1 
$ git log --oneline           

複制

Git Pro深入淺出(二)
$ git status           

複制

Git Pro深入淺出(二)

說明:第二次送出的test2被重置到了初始狀态(上述示例為“Untracked”)!HEAD指針重新指向了第一次送出的commitID。回退到某個版本,隻保留源碼,回退commit和index資訊。

(4)reset常用示例

A). 回退add操作

$ git add test
$ git reset HEAD test           

複制

說明:可以将test從“已暫存”狀态(Index區)復原到初始狀态。

B). 回退最後一次送出

$ git add test
$ git commit -m"Add test"
$ git reset --soft HEAD^           

複制

說明:可以将test從“已送出”狀态變為“已暫存”狀态。

C). 回退最近幾次送出,并把這幾次送出放到新分支上

$ git branch topic #已目前分支為基礎,建立分支topic
$ git reset --hard HEAD~2 #在目前分支上復原送出
$ git checkout topic           

複制

說明:通過臨時分支來保留送出,然後在目前分支上做硬復原。

D). 将本地的狀态回退到和遠端一樣

$ git reset --hard origin/devlop           

複制

E). 回退到某個版本送出

$ git reset 497e350           

複制

說明:497e350為某commitID。目前HEAD會指向497e350,在497e350其之後送出的内容會被回退到初始狀态。

F). 要想在develop分支,但錯誤地送出到了maser分支

git checkout master
git add .
git commit -m"..."
git reset --mixed HEAD~1
git stash
git checkout develop
git stash pop           

複制

8. 進階合并

合并和送出并無不同!!!

在Git中合并是相當容易的,不像其他的版本控制系統,Git 并不會嘗試過于聰明的合并沖突解決方案。Git的哲學是聰明地決定無歧義的合并方案,但是如果有沖突,它不會嘗試智能地自動解決它。

(1)合并沖突

首先,在做一次可能有沖突的合并前盡可能保證工作目錄是幹淨的。如果你有正在做的工作,要麼送出到一個臨時分支要麼儲藏它。這使你可以撤消在這裡嘗試做的任何事情。

# 忽略任意”數量“的已有空白的修改
$ git merge -Xignore-all-space whitespace
# 忽略所有空白修改
$ git merge -Xignore-space-change whitespace           

複制

(2)手動合并

當在不同分支或同一分支不同開發者同時修改了同一檔案,會産生沖突。這時,我們隻想保留某人的修改。

示例:

A同學在develop分支上對test.js進行了修改
$ git checkout develop
$ vi test.js
$ git add test.js
$ git commit -m"Mod test"
$ git push origin develop           

複制

B同學在maser分支上對test.js進行了修改
$ git checkout master
$ vi test.js
$ git add test.js
$ git commit -m"Mod test"
$ git push origin master           

複制

C同學此時切換到master分支,但隻想保留A同學的修改
$ git checkout master
$ git pull
$ git merge --no-ff develop
#由于對同一檔案進行了修改,此時會産生沖突
$ git checkout --theirs test.js   #隻保留A同學的修改
$ git diff --ours              #檢視和B同學修改的差别
$ git checkout --ours test.js  #隻保留B同學的修改
$ git diff --theirs                #檢視和A同學修改的差别           

複制

注意:在把握不好哪個是ours的時候,有個簡單的方法就是打開那個檔案,HEAD代表ours。

Git Pro深入淺出(二)

(3)撤銷合并

假設現在在一個特性分支上工作,不小心将其合并到master中。

Git Pro深入淺出(二)

方式一:修複引用

如果這個不想要的合并送出隻存在于你的本地倉庫中,最簡單且最好的解決方案是移動分支到你想要它指向的地方。

# 移動到合并前的送出點
$ git reset --hard HEAD~1           

複制

Git Pro深入淺出(二)

這個方法的缺點是它會重寫曆史,在一個共享的倉庫中這會造成問題的。如果其他人已經有你将要重寫的送出,你應當避免使用 reset。 如果有任何其他送出在合并之後建立了,那麼這個方法也會無效;移動引用實際上會丢失那些改動。

方式二:還原送出

撤銷上次送出的所有修改

$ git revert -m 1 HEAD           

複制

-m 1 标記指出 “mainline” 需要被保留下來的父結點。上述示例為以上次送出的結點為目前主線父節點。同理:

$ git revert -m 1 HEAD~3

表示最近3次的送出會被幹掉。

Git Pro深入淺出(二)

新的送出 ^M 與 C6 有完全一樣的内容,是以從這兒開始就像合并從未發生過,除了“現在還沒合并”的送出依然在 HEAD 的曆史中。

(4)快速合并

預設情況下,當 Git 看到兩個分支合并中的沖突時,它會将合并沖突标記添加到你的代碼中并标記檔案為沖突狀态來讓你解決。 如果你希望 Git 簡單地選擇特定的一邊并忽略另外一邊而不是讓你手動合并沖突,你可以傳遞給 merge 指令一個 -Xours 或 -Xtheirs 參數。

$ git merge -Xours develop           

複制

9. Rerere

rerere(“reuse recorded resolution”)它允許你讓Git記住解決一個塊沖突的方法,這樣在下一次看到相同沖突時,Git可以為你自動地解決它。

(1)啟用rerere

方式一:運作配置項,開啟

$ git config --global rerere.enabled true           

複制

方式二:也通過在特定的倉庫中建立

.git/rr-cache

目錄來開啟它

注意:設定選項更幹淨并且可以應用到全局,推薦使用配置項開啟

(2)示例

步驟一:制造沖突

在develop和master分支上,同時對test.js檔案中的同一行進行修改

develop分支修改為:console.log(“develop”)

master分支修改為:console.log(“master”)

步驟二:産生沖突,并手動解決

切換到master分支,合并develop分支的内容

$ git checkout master
$ git merge --no-ff develop           

複制

Git Pro深入淺出(二)
Git Pro深入淺出(二)

發現其比正常合并沖突會多一行“Recorded preimage for FILE”的提示。

$ git rerere status
$ git rerere diff           

複制

Git Pro深入淺出(二)

顯示解決方案的目前狀态、開始解決前與解決後的樣子

$ git ls-files -u           

複制

Git Pro深入淺出(二)

顯示沖突檔案的之前、左邊與右邊版本。可以通過下述指令,檢索出對應版本檔案:

$ git show :1:test.js > test.common.js
$ git show :2:test.js > test.ours.js
$ git show :3:test.js > tset.theirs.js           

複制

步驟三:送出解決的沖突

我們将兩個console同時留下,并送出

$ git add .
$ git commit -m"Mod conflict"
$ git push origin master           

複制

步驟四:再次制造相同沖突(重複步驟一)

步驟五:産生沖突

$ git checkout master
$ git merge --no-ff develop           

複制

Git Pro深入淺出(二)

發現其會按照上次的解決方案,自動解決沖突。

Git Pro深入淺出(二)

無需手動解決沖突,方可直接add、commit。

(3)恢複檔案到沖突狀态

rerere可以幫我們按之前的解決方案,解決曆史出現的沖突。如果,我們不想按曆史的方案解決,該如何處理呢?

$ git checkout --conflict=merge test.js           

複制

Git Pro深入淺出(二)

10. 使用Git調試

Git提供了兩個工具輔助我們在項目中出現問題的時候幫助我們找到bug或者錯誤。

(1)檔案标注

如果你在追蹤代碼中的一個bug,并且想知道是什麼時候以及為何會引入,檔案标注通常是最好用的工具。

它展示了檔案中每一行最後一次修改的送出。

$ git blame -L 75,80 server/app.js           

複制

Git Pro深入淺出(二)

其中,-L選項來限制輸出範圍

另一件比較酷的事情是Git不會顯式地記錄檔案的重命名。它會記錄快照,然後在事後嘗試計算出重命名的動作。這其中有一個很有意思的特性就是你可以讓Git找出所有的代碼移動。如果你在

git blame

後面加上一個 -C,Git會分析你正在标注的檔案,并且嘗試找出檔案中從别的地方複制過來的代碼片段的原始出處。

$ git blame -C -L 75,80 server/app.js           

複制

(2)二分查找

假設你剛剛線上上環境部署了你的代碼,接着收到一些bug回報,但這些bug在你之前的開發環境裡沒有出現過,這讓你百思不得其解。 你重新檢視了你的代碼,發現這個問題是可以被重制的,但是你不知道哪裡出了問題。你可以用二分法來找到這個問題。

步驟一:啟動二分查找,并告知Git目前所在的送出是有問題的

$ git bisect start
$ git bisect bad            

複制

步驟二:告訴bisect已知的最後一次正常狀态是哪次送出

$ git bisect good [good_commit]           

複制

此指令會告知你,在你标記為正常的送出和目前的錯誤版本之間有大約送出的數。

步驟三:git自動檢出Git檢出中間的那個送出,然後需你測試驗證是否有問題

  • 如果還存在,說明問題是在這個送出之前引入的;
  • 如果問題不存在,說明問題是在這個送出之後引入的。
# good辨別測試ok
$ git bisect good
# bad辨別測試error
$ git bisect bad           

複制

注意:在辨別檢索出來的送出是否有問題時,git會傳回給我們類似“Bisecting: 3 revisions left to test after this”這樣的提示。

步驟四:重複上述第三步驟,知道發現錯誤送出

步驟五:成功找出錯誤送出,重置你的HEAD指針

$ git bisect reset           

複制

注意:當你完成這些操作之後,必須重置HEAD,否則你會停留在一個很奇怪的狀态。

感慨:通過每次手動測試檢索出來的送出是否正确,是不現實的,工作量太大。但是通過自動化測試腳本會很爽的。如:

# 設定好項目正常以及不正常所在送出的二分查找範圍
# 第一個參數(HEAD)是項目不正常的送出,第二個參數(good_commit)是項目正常的送出
$ git bisect start HEAD [good_commit]
$ git bisect run test-error.sh           

複制

Git會自動在每個被檢出的送出裡執行test-error.sh直到找到第一個項目不正常的送出。你也可以執行make或者make tests或者其他東西來進行自動化測試。

注意:你的測試腳本必須約定:在項目是正常的情況下傳回0,在不正常的情況下傳回非0

(3)總結

當你知道問題是在哪裡引入的情況下檔案标注可以幫助你查找問題;

如果你不知道哪裡出了問題,并且自從上次可以正常運作到現在已經有數十個或者上百個送出,可以使用二分查找定位問題。

11. 子子產品

經常會遇到:某個工作中的項目需要包含并使用另一個項目;想要把它們當做兩個獨立的項目,同時又想在一個項目中使用另一個。

Git通過子子產品來解決這個問題。子子產品允許你将一個Git倉庫作為另一個Git倉庫的子目錄。它能讓你将另一個倉庫克隆到自己的項目中,同時還保持送出的獨立。

(1)添加新的子子產品

# 在項目test中添加子子產品t-module
$ git submodule add https://github.com/381510688/t-module.git           

複制

預設情況下,子子產品會将子項目放到一個與倉庫同名的目錄中,本例中是 “t-module”。如果你想要放到其他地方,那麼可以在指令結尾添加一個不同的路徑。

Git Pro深入淺出(二)

.gitmodules

檔案中儲存了項目 URL 與已經拉取的本地目錄之間的映射。如果有多個子子產品,該檔案中就會有多條記錄。

(2)當你不在子項目目錄中時,Git并不會跟蹤它的内容,而是将它看作該倉庫中的一個特殊送出

$ git diff --cached t-module
$ git diff --cached --submodule           

複制

(3)克隆含有子子產品的項目

方式一:克隆項目,然後初始化更新子項目

$ git clone https://github.com/381510688/test.git           

複制

Git Pro深入淺出(二)

注意:其中有子子產品t-moudle目錄,不過是空的。

# 初始化本地配置檔案
$ git submodule init
# 從該項目中抓取所有資料并檢出父項目中列出的合适的送出
$ git submodule update           

複制

Git Pro深入淺出(二)

方式二:克隆項目,自動初始化并更新倉庫中的每一個子子產品

$ git clone --recursive https://github.com/381510688/test.git           

複制

(4)擷取子子產品最新内容

在項目中使用子子產品的最簡模型,就是隻使用子項目并不時地擷取更新,而并不在你的檢出中進行任何更改。

# 想要在子子產品中檢視新工作,可以進入到目錄中運作 git fetch 與 git merge。
$ git fetch
$ git merge origin/master
# 傳回到主項目,檢視子子產品被更新的清單
git diff --submodule           

複制

Git Pro深入淺出(二)

上述,可以通過一種更簡單的方式:Git将會進入子子產品然後抓取并更新

$ git submodule update --remote t-module           

複制

注意:此指令預設會假定你想要更新并檢出子子產品倉庫的master分支。不過你也可以設定為想要的其他分支。

# 設定從其他分支拉取代碼
$ git config -f .gitmodules submodule.t-module.branch develop
$ git submodule update --remote           

複制

注意:如果不用

-f .gitmodules

選項,那麼它隻會為你做修改。但是在倉庫中保留跟蹤資訊更有意義一些,因為其他人也可以得到同樣的效果。

(5)在子子產品與主項目中同時做修改

到目前為止,當我們運作

git submodule update

從子子產品倉庫中抓取修改時,Git将會獲得這些改動并更新子目錄中的檔案,但是會将子倉庫留在一個稱作“遊離的HEAD”的狀态。這意味着沒有本地工作分支(例如 “master”)跟蹤改動。是以你做的任何改動都不會被跟蹤。

$ git branch -a           

複制

首先,進入每個子子產品并檢出其相應的工作分支。接着,若你做了更改就需要告訴Git它該做什麼,然後運作

git submodule update --remote

來從上遊拉取新工作。你可以選擇将它們合并到你的本地工作中,也可以嘗試将你的工作變基到新的更改上。

$ git checkout master
$ git submodule update --remote --merge           

複制

Git Pro深入淺出(二)
# 可以讓Git在推送到主項目前檢查所有子子產品是否已推送
$ git push --recurse-submodules=check           

複制

如果發現有未推送的檔案,最簡單的方式就是進入每一個子子產品中然後手動推送到遠端倉庫。

(6)子子產品技巧

有一個 foreach 子子產品指令,它能在每一個子子產品中運作任意指令。 如果項目中包含了大量子子產品,這會非常有用。

# 儲存所有子子產品的工作進度
$ git submodule foreach 'git stash'
# 建立一個新分支,并将所有子子產品都切換過去
$ git submodule foreach 'git checkout -b featureA'           

複制

(7)子子產品的問題

問題一:在有子子產品的項目中切換分支可能會造成麻煩

如果你建立一個新分支,在其中添加一個子子產品,之後切換到沒有該子子產品的分支上時,你仍然會有一個還未跟蹤的子子產品目。

$ git checkout -b add-crypto
$ git submodule add https://github.com/chaconinc/CryptoLibrary
$ git commit -am 'adding crypto library'
$ git checkout master
$ git status           

複制

此時,會提示有未捕獲的目錄。

# 移除目錄
$ git clean -fdx           

複制

當再次切回到有子子產品的分支,需要重新初始化子子產品

$ git checkout add-crypto
$ git submodule update --init           

複制

問題二:将子目錄轉換為子子產品的問題

如果你在項目中已經跟蹤了一些檔案,然後想要将它們移動到一個子子產品中,那麼請務必小心。

# 必須要先取消暫存要轉為子子產品的目錄,然後再将其添加為子子產品
$ git rm -r CryptoLibrary
$ git submodule add https://github.com/chaconinc/CryptoLibrary           

複制

注意:這時如果嘗試切換回的分支中那些檔案還在子目錄而非子子產品中時,git會提示一個錯誤

$ git checkout master
error: The following untracked working tree files would be overwritten by checkout:
  CryptoLibrary/Makefile
  CryptoLibrary/includes/crypto.h
  ...
Please move or remove them before you can switch branches.
Aborting           

複制

你可以強制切換,但是要小心,如果其中還有未儲存的修改,這個指令會把它們覆寫掉。

$ git checkout -f master           

複制

12. 打包

Git可以将它的資料“打包”到一個檔案中。這在許多場景中都很有用。有可能你的網絡中斷了、你不在辦公網中并且出于安全考慮沒有給你接入内網的權限等等導緻你無法push代碼,但是你又想将你的代碼共享給别人。這些情況下

git bundle

就會很有用。

bundle指令會将

git push

指令所傳輸的所有内容打包成一個二進制檔案,你可以将這個檔案通過郵件或者閃存傳給其他人,然後解包到其他的倉庫中。

(1)方式一:整個倉庫打包

# 該檔案包含了所有重建該倉庫master分支所需的資料
$ git bundle create repo.bundle HEAD master           

複制

Git Pro深入淺出(二)
# 從檔案中克隆出一個目錄,就像從一個URL克隆一樣
$ git clone repo.bundle repo           

複制

Git Pro深入淺出(二)

注意:如果你希望這個倉庫可以在别處被克隆,你應該像上述例中那樣增加一個HEAD引用

(2)方式二:僅僅打包變更的部分

我們繼續在上述導出檔案基礎上生成的倉庫中做兩次送出。

步驟一:檢視在我們的master分支而不在原始倉庫中的送出

$ git log --oneline master ^origin/master           

複制

Git Pro深入淺出(二)

注意:圖中提示資訊“commit1”誤寫為了“commmit1”

步驟二:指定要打包的送出區間,并打包成指定檔案名的檔案

$ git bundle create commits.bundle master ^4df6152           

複制

注意:

  • 4df6152為commit2前一次送出的ID,可以通過

    git log

    檢視
  • 可以将這個檔案導入到原始的倉庫中,即使在這期間已經有其他的工作送出到這個倉庫中。

步驟三:将導出的檔案通過郵件或者U盤傳給别人

步驟四:擷取檔案中的内容

将接受到的檔案,拷貝到和項目同目錄下

# 檢查這個檔案是否是一個合法的Git包,是否擁有共同的祖先來導入
$ git bundle verify ../commits.bundle
# 檢視這邊包裡可以導入哪些分支
$ git bundle list-heads ../commits.bundle           

複制

Git Pro深入淺出(二)
# 從包中取出master分支到我們倉庫中的other-master分支
$ git fetch ../commits.bundle master:other-master           

複制

Git Pro深入淺出(二)

注意:上述不能合并到master分支

步驟五:剩下的工作,就隻将other-master分支的内容合并到master分支上了

$ git merge --no-ff other-master
# 如果有沖突,可以手動解決沖突或者選擇接受一方的送出
$ git checkout --ours/theirs test.js           

複制

13. 替換

Git對象是不可改變的,但它提供一種有趣的方式來用其他對象假裝替換資料庫中的Git對象。

replace指令可以讓你在Git中指定一個對象并可以聲稱“每次你遇到這個Git對象時,假裝它是其他的東西”。在你用一個不同的送出替換曆史中的一個送出時,這會非常有用。

示例:

步驟一:假如擁有5個送出的簡單倉庫

$ git log --oneline
ef989d8 fifth commit
c6e1e95 fourth commit
9c68fdc third commit
945704c second commit
c1822cf first commit           

複制

步驟二:将其分成拆分成兩條曆史

$ git branch history c6e1e95
$ git log --oneline --decorate
ef989d8 (HEAD, master) fifth commit
c6e1e95 (history) fourth commit
9c68fdc third commit
945704c second commit
c1822cf first commit           

複制

Git Pro深入淺出(二)

步驟三:把這個新的history分支推送到我們新倉庫的master分支

$ git remote add project-history https://github.com/schacon/project-history
$ git push project-history history:master           

複制

步驟四:建立基礎送出

$ echo 'get history from blah blah blah' | git commit-tree 9c68fdc^{tree}
622e88e9cbfbacfb75b5279245b9fb38dfea10cf           

複制

注意:我們需要選擇一個點去拆分,對于上例而言是第三個送出(SHA是 9c68fdc),是以我們的送出将基于此送出樹。

commit-tree

會傳回一個全新的、無父節點的SHA送出對象。

Git Pro深入淺出(二)

步驟五:有一個基礎送出,可以通過指令來将剩餘的曆史變基到基礎送出之上

$ git rebase --onto 622e88 9c68fdc           

複制

Git Pro深入淺出(二)

到目前為止,我們已經用基礎送出重寫了最近的曆史,基礎送出包括如何重新組成整個曆史的說明。我們可以将新曆史推送到新項目中,當其他人克隆這個倉庫時,他們僅能看到最近兩次送出以及一個包含上述說明的基礎送出。

如果,想擷取整個項目的曆史該如何做???

在克隆這個截斷後的倉庫後為了得到曆史資料,需要添加第二個遠端的曆史版本庫并對其做擷取操作:

# 擷取最新送出
$ git clone https://github.com/schacon/project
# 擷取曆史送出
$ git remote add project-history https://github.com/schacon/project-history
$ git fetch project-history           

複制

這樣,在master分支中擁有最近的送出并且在

project-history/master

分支中擁有過去的送出

# 最新送出
$ git log --oneline master
e146b5f fifth commit
81a708d fourth commit
622e88e get history from blah blah blah
# 曆史送出
$ git log --oneline project-history/master
c6e1e95 fourth commit
9c68fdc third commit
945704c second commit
c1822cf first commit           

複制

最後一步:将master分支中的第四個送出替換為project-history/master分支中的“第四個”送出

$ git replace 81a708d c6e1e95
# 檢視master分支中的曆史資訊
$ git log --oneline master
e146b5f fifth commit
81a708d fourth commit
9c68fdc third commit
945704c second commit
c1822cf first commit           

複制

Git Pro深入淺出(二)

綜上所述,我們不用改變上遊的SHA-1就能用一個送出來替換曆史中的所有不同的送出,并且所有的工具(bisect,blame 等)也都可以使用!!!