ProGit 讀書筆記
Table of Contents
- 一、記錄每次更新到倉庫
* 移除檔案 - 二、檢視提交歷史
* 限制輸出長度 - 三、撤消操作
* 取消暫存的檔案
* 撤消對檔案的修改 - 四、遠端倉庫的使用
* 檢視遠端倉庫
* 新增遠端倉庫
* 從遠端倉庫中抓取與拉取 - 五、打標籤
* 列出標籤
* 附註標籤
* 輕量標籤
* 後期打標籤
* 共享標籤
* 檢出標籤 - 六、分支的新建與合併
* 新建分支
* 合併分支 - 七、分支管理
- 八、遠端分支
* 推送
* 跟蹤分支
* 拉取
* 刪除遠端分支 - 九、變基
* 變基的基本操作
* 更有趣的變基例子
* 變基的風險 & 用變基解決變基 - 十、分散式工作流程
- 十一、向一個專案貢獻
* 提交準則
* 私有管理團隊
* 派生的公開專案 - 十二、選擇修訂版本
* 引用日誌
* 祖先引用
* 提交區間
* 多點
* 三點 - 十三、互動式暫存
- 十四、儲藏與清理
* 儲藏工作
* 創造性的儲藏
* 從儲藏建立一個分支 - 十五、搜尋
* Git Grep - 十六、重寫歷史
* 修改最後一次提交
* 修改多個提交資訊
* 重新排序提交
* 壓縮提交
* 拆分提交
* 核武器級選項:filter-branch
* 從每一個提交移除一個檔案
* 全域性修改郵箱地址 - 十七、重置揭密
* 三棵樹 HEAD, Index, Working Directory
* 重置的作用
* 通過路徑來重置
* 壓縮
* 檢出
* 總結 - 十八、高階合併
* 合併衝突 - 十九、使用 Git 除錯
* 檔案標註
* 二分查詢 - 二十、配置 Git
* 外部的合併與比較工具
* 格式化與多餘的空白字元 - 二十一、Git 屬性
* 合併策略 - 二十二、Git 鉤子
* 客戶端鉤子 - 二十三、Git 物件
* 樹物件
* 提交物件 - 二十四、Git 引用
* HEAD 引用 - 二十五、維護與資料恢復
* 資料恢復
* 移除物件
一、記錄每次更新到倉庫
移除檔案
-
要從 Git 中移除某個檔案,就必須要從暫存區域移除,然後提交。可以用
git rm
命令完成此項工作,並連帶從工作目錄中刪除指定的檔案,這樣以後就不會出現在未跟蹤檔案清單中了。 -
如果刪除之前修改過並且已經放到暫存區域的話,
git rm
必須要用強制刪除選項-f
-
如果想讓檔案保留在磁碟,但是並不想讓 Git 繼續跟蹤。當你忘記新增
.gitignore
檔案,不小心把一個很大的日誌檔案或一堆 .a 這樣的編譯生成檔案新增到暫存區時,使用--cached
選項:$ git rm --cached README
-
要在 Git 中對檔案改名,可以使用 Git 的
mv
命令$ git mv file_from file_to
二、檢視提交歷史
限制輸出長度
-
git log
用--author
選項顯示指定作者的提交,用--grep
選項搜尋提交說明中的關鍵字。(請注意,如果要得到同時滿足這兩個選項搜尋條件的提交,就必須用--all-match
選項。否則,滿足任意一個條件的提交都會被匹配出來) -
另一個非常有用的篩選選項是
-S
,可以列出那些新增或移除了某些字串的提交。比如說,你想找出新增或移除了某一個特定函式的引用的提交,你可以這樣使用:$ git log -Sfunction_name
-
限制
git log
輸出的選項限制 git log 輸出的選項
三、撤消操作
-
提交完了才發現漏掉了幾個檔案沒有新增,或者提交資訊寫錯了。此時,可以執行帶有
--amend
選項的提交命令嘗試重新提交:$ git commit --amend
取消暫存的檔案
- 使用
git reset HEAD <file>...
來取消暫存
撤消對檔案的修改
git checkout -- <file>... git checkout -- [file]
四、遠端倉庫的使用
檢視遠端倉庫
-
git remote -v
會顯示需要讀寫遠端倉庫使用的 Git 儲存的簡寫與其對應的 URLimage
-
想要檢視某一個遠端倉庫的更多資訊,可以使用
git remote show [remote-name]
命令。它會列出遠端倉庫的 URL 與跟蹤分支的資訊。這些資訊非常有用,它告訴你正處於 master 分支,並且如果執行git pull
,就會抓取所有的遠端引用,然後將遠端 master 分支合併到本地 master 分支image
git push
會自動地推送到哪一個遠端分支。它也會列出哪些遠端分支不在你的本地,哪些遠端分支已經從伺服器上移除了,還有當你執行git pull
時哪些分支會自動合併 -
如果因為一些原因想要移除一個遠端倉庫 - 你已經從伺服器上搬走了或不再想使用某一個特定的映象了,又或者某一個貢獻者不再貢獻了 - 可以使用
git remote rm <remote_name>
新增遠端倉庫
- 執行
git remote add <shortname> <url>
新增一個新的遠端 Git 倉庫
從遠端倉庫中抓取與拉取
-
如果你想拉取 Paul 的倉庫中有但你沒有的資訊,可以執行
git fetch pb
-
如果你有一個分支設定為跟蹤一個遠端分支,可以使用
git pull
命令來自動的抓取然後合併遠端分支到當前分支。預設情況下,git clone
命令會自動設定本地 master 分支跟蹤克隆的遠端倉庫的 master 分支(或不管是什麼名字的預設分支)。執行git pull
通常會從最初克隆的伺服器上抓取資料並自動嘗試合併到當前所在的分支。 -
另一種簡單的方法是使用
git pull --rebase
命令而不是直接git pull
。又或者你可以自己手動完成這個過程,先git fetch
,再git rebase
。 -
如果你習慣使用
git pull
,同時又希望預設使用選項--rebase
,你可以執行這條語句git config --global pull.rebase true
來更改pull.rebase
的預設配置。image
五、打標籤
- Git 使用兩種主要型別的標籤: 輕量標籤 (lightweight)與 附註標籤 (annotated)
列出標籤
-
列出已有的標籤是非常簡單直觀的。只需要輸入
git tag
$ git tag v0.1 v1.3
附註標籤
-
建立一個附註標籤最簡單的方式是當你在執行 tag 命令時指定
-a
選項:$ git tag -a v1.4 -m 'my version 1.4' $ git tag v0.1 v1.3 v1.4
-
git show
命令可以看到標籤資訊與對應的提交資訊
輕量標籤
-
輕量標籤本質上是將提交校驗和儲存到一個檔案中 - 沒有儲存任何其他資訊。建立輕量標籤,不需要使用
-a
、-s
或-m
選項,只需要提供標籤名字:$ git tag v1.5 $ git tag v0.1 v1.3 v1.4 v1.5
後期打標籤
-
假設在 v1.2 時你忘記給專案打標籤,也就是在 “updated rakefile” 提交。你可以在之後補上標籤。要在哪個提交上打標籤,你需要在命令末尾指定提交的校驗和:
$ git tag -a v1.2 9fceb02
共享標籤
-
預設情況下,git push 命令並不會傳送標籤到遠端倉庫伺服器上。在建立完標籤後你必須顯式地推送標籤到共 享伺服器上。
git push origin [tag_name]
-
如果想要一次性推送很多標籤,也可以使用帶有
git push origin --tags
,這將會把所有不在遠端倉庫伺服器上的標籤全部推送。
檢出標籤
-
在 Git 中你並不能真的檢出一個標籤,因為它們並不能像分支一樣來回移動。如果你想要工作目錄與倉庫中特定的標籤版本完全一樣,可以使用
git checkout -b [branchname] [tagname]
在特定的標籤上建立一個新分支$ git checkout -b version2 v2.0.0 Switched to a new branch 'version2'
六、分支的新建與合併
新建分支
-
在你切換分支之前,保持好一個乾淨的狀態。有一些方法: 儲存進度 (stashing) 和 修補提交 (commit amending))
-
在合併的時候,你應該注意到了" 快進(fast-forward) "這個詞。由於當前 master 分支所指向的提交是你當前提交的直接上游,所以 Git 只是簡單的將指標向前移動。換句話說,當你試圖合併兩個分支時,如果順著一個分支走下去能夠到達另一個分支,那麼 Git 在合併兩者的時候,只會簡單的將指標向前推進 (指標右移),因為這種情況下的合併操作沒有需要解決的分歧。
合併分支
-
你的開發歷史從一個更早的地方開始分叉開來(diverged)。因為,master 分支所在提交併不是 iss53 分支所在提交的直接祖先,Git 不得不做一些額外的工作。出現這種情況的時候,Git 會使用兩個分支的末端所指的快照(C4 和 C5)以及這兩個分支的工作祖先(C2),做一個簡單的三方合併。 高亮 [57]
-
Git 將此次三方合併的結果做了一個新的快照並且自動建立一個新的提交指向它。這個被稱作一次合併提交,它的特別之處在於他有不止一個父提交 高亮 [58]
七、分支管理
-
如果需要檢視每一個分支的最後一次提交,可以執行
git branch -v
命令$ git branch -v iss53 93b412c fix javascript issue
-
master 7a98805 Merge branch 'iss53'
testing 782fd34 add scott to the author list in the readmes
- 如果分支包含了還未合併的工作,嘗試使用
git branch -d
命令刪除它時會失敗,如果真的想要刪除分支並丟掉那些工作,可以使用-D
選項強制刪除。
八、遠端分支
-
遠端跟蹤分支是遠端分支狀態的引用。它們是你不能移動的本地引用,當你做任何網路通訊操作時,它們會自動移動。
-
如果你在本地的 master 分支做了一些工作,然而在同一時間,其他人推送提交到 git.ourcompany.com 並更新了它的 master 分支,那麼你的提交歷史將向不同的方向前進。也許,只要你不與 origin 伺服器連線,你的 origin/master 指標就不會移動
-
如果要同步你的工作,執行 git fetch origin 命令。這個命令查詢 “origin” 是哪一個伺服器(在本例中,它是 git.ourcompany.com),從中抓取本地沒有的資料,並且更新本地資料庫,移動 origin/master 指標指向新的、更新後的位置
推送
-
當你想要公開分享一個分支時,需要將其推送到有寫入許可權的遠端倉庫上。本地的分支並不會自動與遠端倉庫同步 - 你必須顯式地推送想要分享的分支。這樣,你就可以把不願意分享的內容放到私人分支上,而將需要和別人協作的內容推送到公開分支。
-
你也可以執行
git push origin serverfix:serverfix
,它會做同樣的事 - 相當於它說,“推送本地 的serverfix
分支,將其作為遠端倉庫的serverfix
分支”。可以通過這種格式來推送本地分支到一個命名不相同的遠端分支。如果並不想讓遠端倉庫上的分支叫做
serverfix
,可以執行git push origin serverfix:awesomebranch
來將本地的serverfix
分支推送到遠端倉庫上的awesomebranch
分支。 -
要特別注意的一點是當抓取到新的遠端跟蹤分支時,本地不會自動生成一份可編輯的副本(拷貝)。即是說,這種情況下,不會有一個新的
serverfix
分支 - 只有一個不可以修改的 origin/serverfix 指標。 -
可以執行
git merge origin/serverfix
將這些工作合併到當前所在的分支。如果想要在自己的 serverfix 分支上工作,可以將其建立在遠端跟蹤分支之上:$ git checkout -b serverfix origin/serverfix
跟蹤分支
-
從一個遠端跟蹤分支檢出一個本地分支會自動建立一個叫做 “跟蹤分支”(有時候也叫做 “上游分支”)。跟蹤分支是與遠端分支有直接關係的本地分支。如果在一個跟蹤分支上輸入 git pull,Git 能自動地識別去哪個伺服器上抓取、合併到哪個分支。
-
最簡單的就是,執行
git checkout -b [branch] [remotename]/[branch]
。這是一個十分常用的操作,所以 Git 提 供了 --track 快捷方式:$ git checkout --track origin/serverfix Branch serverfix set up to track remote branch serverfix from origin. Switched to a new branch 'serverfix'
-
如果想要將本地分支與遠端分支設定為不同名字,你可以輕鬆地增加一個不同名字的本地分支:
$ git checkout -b sf origin/serverfix
-
設定已有的本地分支跟蹤一個剛剛拉取下來的遠端分支,或者想要修改正在跟蹤的上游分支,你可以在任意時間使用 -u 或 --set-upstream-to 選項執行 git branch 來顯式地設定。
$ git branch -u origin/serverfix
-
當設定好跟蹤分支後,可以通過
@{upstream}
或@{u}
快捷方式來引用它。 -
如果想要檢視設定的所有跟蹤分支,可以使用
git branch
的-vv
選項。$ git branch -vv iss53 7e424c3 [origin/iss53: ahead 2] forgot the brackets master 1ae2a45 [origin/master] deploying index fix * serverfix f8674d9 [teamone/server-fix-good: ahead 3, behind 1] this should do it
如果想要統計最新的領先與落後數字,需要在執行此命令前抓取 所有的遠端倉庫。可以像這樣做:
$ git fetch --all; git branch -vv
拉取
- git pull 在大多數情況下它的含義是一個 git fetch 緊接著一個 git merge 命令。
刪除遠端分支
-
可以執行帶有
--delete
選項的git push
命令來刪除一個遠端分支。如果想要從伺服器上刪除 serverfix 分支,執行下面的命令:$ git push origin --delete serverfix To https://github.com/schacon/simplegit - [deleted] serverfix
九、變基
變基的基本操作
-
有一種方法:你可以提取在 C4 中引入的補丁和修改,然後在 C3 的基礎上再應用一次。在 Git 中,這種操作就叫做 變基。你可以使用 rebase 命令將提交到某一分支上的所有修改都移至另一分支上,就好像“重新播放”一樣。 在上面這個例子中,執行:
$ git checkout experiment $ git rebase master First, rewinding head to replay your work on top of it... Applying: added staged command
它的原理是首先找到這兩個分支(即當前分支 experiment、變基操作的目標基底分支 master)的最近共同祖先 C2,然後對比當前分支相對於該祖先的歷次提交,提取相應的修改並存為臨時檔案,然後將當前分支指向目標基底 C3, 最後以此將之前另存為臨時檔案的修改依序應用。 高亮 [80]
-
無論是通過變基,還是通過三方合併,整合的最終結果所指向的快照始終是一樣的,只不過提交歷史不同罷了。變基是將一系列提交按照原有次序依次應用到另一分支上,而合併是把最終結果合在一起。
更有趣的變基例子
-
假設你希望將 client 中的修改合併到主分支併發布,但暫時並不想合併 server 中的修改,因為它們還需要經過更全面的測試。這時,你就可以使用 git rebase 命令的 --onto 選項,選中在 client 分支裡但不在 server 分支裡的修改(即 C8 和 C9),將它們在 master 分支上重演:
$ git rebase --onto master server client
以上命令的意思是:“取出 client 分支,找出處於 client 分支和 server 分支的共同祖先之後的修改,然 後把它們在 master 分支上重演一遍”。
-
接下來你決定將 server 分支中的修改也整合進來。使用
git rebase [basebranch] [topicbranch]
命令可以直接將特性分支(即本例中的 server)變基到目標分支(即master)上。這樣做能省去你先切換到 server 分支,再對其執行變基命令的多個步驟。$ git rebase master server
變基的風險 & 用變基解決變基
-
如果你習慣使用
git pull
,同時又希望預設使用選項--rebase
,你可以執行這條語句git config --global pull.rebase true
來更改pull.rebase
的預設配置。 -
假如你在那些已經被推送至共用倉庫的提交上執行變基命令,並因此丟棄了一些別人的開發所基於的提交,那就有大麻煩了,你的同事也會因此鄙視你。
十、分散式工作流程
- 整合管理者工作流 Git 允許多個遠端倉庫存在,使得這樣一種工作流成為可能:每個開發者擁有自己倉庫的寫許可權和其他所有人倉庫的讀許可權。
十一、向一個專案貢獻
提交準則
-
首先,你不會想要把空白錯誤(根據
git help diff
的描述,結合下面給出的圖片,空白錯誤是指行尾的空格、Tab 製表符,和行首空格後跟 Tab 製表符的行為)提交上去。Git 提供了一個簡單的方式來檢查這點 - 在提交前,執行
git diff --check
,它將會找到可能的空白錯誤並將它們為你列出來。 -
要知道必須合併什麼進入,工作才能推送
git log --no-merges issue54..origin/master
issue54..origin/master
語法是一個日誌過濾器,要求 Git 只顯示所有在後面分支(在本例中是origin/master
)但不在前面分支(在本例中是issue54
)的提交的列表。
私有管理團隊
-
需要將在 featureB 分支上合併的工作推送到伺服器上的 featureBee 分支。她可以通過指定 本地分支加上冒號(:)加上遠端分支給 git push 命令來這樣做:
$ git push -u origin featureB:featureBee ... To jessica@githost:simplegit.git fba9af8..cd685d1 featureB -> featureBee
注意
-u
標記;這是--set-upstream
的簡寫,該標記會為之後輕鬆地推送與拉取配置分支
派生的公開專案
- 因為你將分支變基了,所以必須為推送命令指定 -f 選項,這樣才能將伺服器上有一個不是它的後代的提交的 featureA 分支替換掉。一個替代的選項是推送這個新工作到伺服器上的一個不同分支(可能稱作 featureAv2)。
十二、選擇修訂版本
引用日誌
-
當你在工作時, Git 會在後臺儲存一個引用日誌(reflog),引用日誌記錄了最近幾個月你的 HEAD 和分支引用所 指向的歷史。 你可以使用
git reflog
來檢視引用日誌。 -
如果你想檢視倉庫中 HEAD 在五次前的所指向的提交,你可以使用
@{n}
來引用 reflog 中輸出的提交記錄。$ git show HEAD@{5}
可以執行
git log -g
來檢視類似於git log
輸出格式的引用日誌資訊 -
引用日誌只存在於本地倉庫,一個記錄你在你自己的倉庫裡做過什麼的日誌。其他人拷貝的倉庫裡的引用日誌不會和你的相同;而你新克隆一個倉庫的時候,引用日誌是空的,因為你在倉庫裡還沒有操 作。
git show HEAD@{2.months.ago}
這條命令只有在你克隆了一個專案至少兩個月時才會有用——如果你是五分鐘前克隆的倉庫,那麼它將不會有結果返回。
祖先引用
-
祖先引用是另一種指明一個提交的方式。如果你在引用的尾部加上一個
^
, Git 會將其解析為該引用的上一個提交。可以使用git show HEAD^
來檢視上一個提交,也就是 “HEAD 的父提交” -
可以在
^
後面新增一個數字——例如 d921970^2 代表 “d921970 的第二父提交”這個語法只適用於合併 (merge)的提交,因為合併提交會有多個父提交。第一父提交是你合併時所在分支,而第二父提交是你所合併的分支 -
另一種指明祖先提交的方法是
~
。同樣是指向第一父提交,因此HEAD~
和HEAD^
是等價的。而區別在於你在後面加數字的時候。HEAD~2
代表 “第一父提交的第一父提交” —— Git 會根據你指定的次數獲取對應的第一父提交。 -
HEAD~3
也可以寫成HEAD^^^
,也是第一父提交的第一父提交的第一父提交。也可以組合使用這兩個語法 —— 你可以通過HEAD~3^2
來取得之前引用的第二父提交(假設它是一個合併提交)
提交區間
-
image
master..experiment
來讓 Git 顯示這些提交。也就是 “ 在 experiment 分支中而不在 master 分支中的提交 ”。
這可以讓你保持 experiment 分支跟隨最新的進度以及檢視你即將合併的內容。 -
另一個常用的場景是檢視你即將推送到遠端的內容:
$ git log origin/master..HEAD
這個命令會輸出在你當前分支中而不在遠端 origin 中的提交。
如果你留空了其中的一邊, Git 會預設為 HEAD。例如,
git log origin/master..
將會輸出與之前例子相同的結果 —— Git 使用 HEAD 來代替留空的一邊 。
多點
-
Git 允許你在任意引用前加上
^
字元或者--not
來指明你不希望提交被包含其中的分支。因此下列3個命令是等價的:$ git log refA..refB $ git log ^refA refB $ git log refB --not refA
-
這個語法可以幫你在查詢中指定超過兩個的引用,例如想檢視所有被 refA 或 refB 包含的但是不被 refC 包含的提交,你可以輸入下面中的任意一個命令
$ git log refA refB ^refC $ git log refA refB --not refC
三點
-
最後一種主要的區間選擇語法是三點,這個語法可以選擇出被兩個引用中的一個包含但又不被兩者同時包含的提交。
$ git log master...experiment F E D C
可以使用引數
--left-right
,它會顯示每個提交到底處於哪一側的分支。$ git log --left-right master...experiment < F < E > D > C
十三、互動式暫存
執行 git add
時使用 -i
或者 --interactive
選項,Git 將會進入一個互動式終端模式
十四、儲藏與清理
儲藏工作
-
將新的儲藏推送到棧上,執行
git stash
或git stash save
-
要檢視儲藏的東西,可以使用
git stash list
:$ git stash list stash@{0}: WIP on master: 049d078 added the index file stash@{1}: WIP on master: c264051 Revert "added file_size" stash@{2}: WIP on master: 21d80a5 added number to log
-
將你剛剛儲藏的工作重新應用:
git stash apply
。如果想要應用其中一個更舊的儲藏,可以通過名字指定它,像這樣:git stash apply stash@{2}
。如果不指定一個儲藏,Git 認為指定的是最近的儲藏. -
可以在一個分支上儲存一個儲藏,切換到另一個分支,然後嘗試重新應用這些修改。當應用儲藏時工作目錄中也可以有修改與未提交的檔案 - 如果有任何東西不能幹淨地應用,Git 會產生合併衝突。
-
檔案的改動被重新應用了,但是之前暫存的檔案卻沒有重新暫存。想要那樣的話,必須使用
--index
選項來執行git stash apply
命令,來嘗試重新應用暫存的修改(即存放到相應的暫存區)。 -
應用選項只會嘗試應用暫存的工作 - 在堆疊上還有它。可以執行
git stash drop
加上將要移除的儲藏的名字來移除它。也可以執行
git stash pop
來應用儲藏然後立即從棧上扔掉它。
創造性的儲藏
-
有幾個儲藏的變種可能也很有用。第一個非常流行的選項是
stash save
命令的--keep-index
選項。它告訴 Git 不要儲藏任何你通過 git add 命令已暫存的東西。當你做了幾個改動並只想提交其中的一部分,過一會兒再回來處理剩餘改動時,這個功能會很有用。
-
另一個是像儲藏跟蹤檔案一樣儲藏未跟蹤檔案。預設情況下,
git stash
只會儲藏已經在索引中的檔案。如果指定--include-untracked
或 -u 標記,Git 也會儲藏任何建立的未跟蹤檔案。
從儲藏建立一個分支
-
如果儲藏了一些工作,將它留在那兒了一會兒,然後繼續在儲藏的分支上工作,在重新應用工作時可能會有問題。
如果應用嘗試修改剛剛修改的檔案,你會得到一個合併衝突並不得不解決它。
如果想要一個輕鬆的方式來再次測試儲藏的改動,可以執行
git stash branch
建立一個新分支,檢出儲藏工作時所在的提交,重新在那應用工作,然後在應用成功後扔掉儲藏$ git stash branch testchanges Switched to a new branch "testchanges" # On branch testchanges # Changes to be committed: # (use "git reset HEAD <file>..." to unstage) # # modified: index.html # # Changed but not updated: # (use "git add <file>..." to update what will be committed) # # modified: lib/simplegit.rb # Dropped refs/stash@{0} (f0dfc4d5dc332d1cee34a634182e168c4efc3359)
這是在新分支輕鬆恢復儲藏工作並繼續工作的一個很不錯的途徑。
十五、搜尋
Git Grep
-
Git 提供了一個
grep
命令,你可以很方便地從提交歷史或者工作目錄中查詢一個字串或者正則表示式。相比於一些常用的搜尋命令比如
grep
和ack
,git grep
命令有一些的優點。第一就是速度非常快,第二是你不僅僅可以搜尋工作目錄,還可以搜尋任意的 Git 樹。 -
預設情況下 Git 會查詢你工作目錄的檔案。你可以傳入 -n 引數來輸出 Git 所找到的匹配行行號
$ git grep -n gmtime_r compat/gmtime.c:3:#undef gmtime_r compat/gmtime.c:8: return git_gmtime_r(timep, &result); compat/mingw.c:606:struct tm *gmtime_r(const time_t *timep, struct tm *result) date.c:429: if (gmtime_r(&now, &now_tm))
-
你可以使用
--count
選項來使 Git 輸出概述的資訊,僅僅包括哪些檔案包含匹配以及每個檔案包含了多少個匹配。$ git grep --count gmtime_r compat/gmtime.c:2 compat/mingw.c:1 date.c:1
-
如果你想看匹配的行是屬於哪一個方法或者函式,你可以傳入
-p
選項:$ git grep -p gmtime_r *.c
-
你還可以使用
--and
標誌來檢視複雜的字串組合,也就是在同一行同時包含多個匹配。 -
如果我們想找到
ZLIB_BUF_MAX
常量是什麼時候引入的,我們可以使用-S
選項來顯示新增和刪除該字串的提交。$ git log -SZLIB_BUF_MAX --oneline e01503b zlib: allow feeding more than 4GB in one go ef49a7a zlib: zlib can only process 4GB at a time
十六、重寫歷史
修改最後一次提交
-
如果你已經完成提交,又因為之前提交時忘記新增一個新建立的檔案,想通過新增或修改檔案來更改提交的快照,也可以通過類似的操作來完成。通過修改檔案然後執行
git add
或git rm
一個已追蹤的檔案,隨後執行git commit --amend
拿走當前的暫存區域並使其做為新提交的快照。使用這個技巧的時候需要小心,因為修正會改變提交的 SHA-1 校驗和。它類似於一個小的變基 - 如果已經推送了最後一次提交,就不要修正它。
修改多個提交資訊
-
例如,如果想要修改最近三次提交資訊,或者那組提交中的任意一個提交資訊,將想要修改的最近一次提交的父提交作為引數傳遞給
git rebase -i
命令,即HEAD~2^
或HEAD~3
。記住~3
可能比較容易,因為你正嘗試修改最後三次提交;但是注意實際上指定了以前的四次提交,即想要修改提交的父提交:
$ git rebase -i HEAD~3
再次記住這是一個變基命令 - 在HEAD~3..HEAD
範圍內的每一個提交都會被重寫,無論你是否修改資訊。
重新排序提交
壓縮提交
拆分提交
核武器級選項:filter-branch
從每一個提交移除一個檔案
-
filter-branch
是一個可能會用來擦洗整個提交歷史的工具。為了從整個提交歷史中移除一個叫做 passwords.txt 的檔案,可以使用--tree-filter
選項給filter-branch
:$ git filter-branch --tree-filter 'rm -f passwords.txt' HEAD Rewrite 6b9b3cf04e7c5686a9cb838c3f36a8cb6a0fc2bd (21/21) Ref 'refs/heads/master' was rewritten
全域性修改郵箱地址
十七、重置揭密
三棵樹 HEAD, Index, Working Directory
-
HEAD 是當前分支引用的指標,它總是指向該分支上的最後一次提交。這表示 HEAD 將是下一次提交的父結點。通常,理解 HEAD 的最簡方式,就是將它看做 你的上一次提交 的快照。
-
索引是你的 預期的下一次提交 。我們也會將這個概念引用為 Git 的 “ 暫存區域 ”,這就是當你執行 git commit 時 Git 看起來的樣子。
重置的作用
- 如果指定 --mixed 選項,會撤銷一上次 提交,但還會 取消暫存 所有的東西。於是,我們回滾到了所有 git add 和 git commit 的命令執行之前,所有修改都存在於 Working Directory 。
通過路徑來重置
-
你還可以給 reset 提供一個作用路徑。若指定了一個路徑,reset 會將它的作用範圍限定為指定的檔案或檔案集合。這樣做自然有它的道理,因為 HEAD 只是一個指標,你無法讓它同時指向兩個提交中各自的一部分。不過索引和工作目錄可以部分更新,所以重置會繼續進行第 2、3 步。
假如我們執行git reset file.txt
(這其實是git reset --mixed HEAD file.txt
的簡寫形 式,因為你既沒有指定一個提交的 SHA-1 或分支,也沒有指定--soft
或--hard
),它會:
- 移動 HEAD 分支的指向 (已跳過)
- 讓索引看起來像 HEAD (到此處停止)
所以它本質上只是將 file.txt 從 HEAD 複製到索引中。
壓縮
檢出
-
git reset master
git checkout master
假設我們有 master 和 develop 分支,它們分別指向不同的提交;我們現在在 develop 上(所以 HEAD 指向它)。如果我們執行
git reset master
,那麼 develop 自身現在會和 master 指向同一個提交。而如果我們執行git checkout master
的話,develop 不會移動,HEAD 自身會移動。現在 HEAD 將 會指向 master。image
總結
-
下面的速查表列出了命令對樹的影響。“HEAD” 一列中的 “REF” 表示該命令移動了 HEAD 指向的分支引 用,而`‘HEAD’' 則表示只移動了 HEAD 自身。特別注意 WD Safe? 一列 - 如果它標記為 NO,那麼執行該命令 之前請考慮一下。
image
十八、高階合併
合併衝突
-
git merge --abort
選項會嘗試恢復到你執行合併前的狀態。但當執行命令前,在工作目錄中有未儲藏、未提交的修改時它不能完美處理,除此之外它都工作地很好。 -
一個很有用的工具是帶
--conflict
選項的git checkout
。這會重新檢出檔案並替換合併衝突標記。如果想要重置標記並嘗試再次解決它們的話這會很有用。 可以傳遞給--conflict
引數diff3
或merge
(預設選項)。如果傳給它 diff3,Git 會使用一個略微不同版本的衝突標記:不僅僅只給你 “ours” 和 “theirs” 版本,同時也會有 “base” 版本在中間來給你更多的上下文。$ git checkout --conflict=diff3 hello.rb #! /usr/bin/env ruby def hello <<<<<<< ours puts 'hola world' ||||||| base puts 'hello world' ======= puts 'hello mundo' >>>>>>> theirs end hello()
十九、使用 Git 除錯
檔案標註
-
你可以使用
git blame
標註這個檔案,檢視這個方法每一行的最後修改時間以及是被誰修改的,可以使用 -L 選項來限制輸出範圍 -
有一個很有意思的特性就是你可以讓 Git 找出所有的程式碼移動。如果你在
git blame
後面加上一個-C
,Git 會分析你正在標註的檔案,並且嘗試找出檔案中從別的地方複製過來的程式碼片段的原始出處。比如,你將
GITServerHandler.m
這個檔案拆分為數個檔案,其中一個檔案是GITPackUpload.m
。對GITPackUpload.m
執行帶 -C 引數的blame命令,你就可以看到程式碼塊的原始出處$ git blame -C -L 141,153 GITPackUpload.m
二分查詢
-
首先執行
git bisect start
來啟動,接著執行git bisect bad
來告訴系統當前你所在的提交是有問題的。然後你必須告訴 bisect 已知的最後一次正常狀態是哪次提交,使用git bisect good [good_commit]
$ git bisect start $ git bisect bad $ git bisect good v1.0 Bisecting: 6 revisions left to test after this [ecb6e1bc347ccecc5f9350d878ce677feb13d3b2] error handling on repo
假設測試結果是沒有問題的,你可以通過
git bisect good
來告訴 Git,然後繼續尋找。你再一次執行測試,發現這個提交下是有問題的,因此你可以通過
git bisect bad
告訴 Git -
當你完成這些操作之後,你應該執行
git bisect reset
重置你的 HEAD 指標到最開始的位置,否則你會停留在一個很奇怪的狀態
二十、配置 Git
外部的合併與比較工具
格式化與多餘的空白字元
二十一、Git 屬性
合併策略
- 通過 Git 屬性,你還能對專案中的特定檔案指定不同的合併策略。一個非常有用的選項就是,告訴 Git 當特定檔案發生衝突時不要嘗試合併它們,而是直接使用你這邊的內容。
二十二、Git 鉤子
客戶端鉤子
-
pre-commit
鉤子在鍵入提交資訊前執行。它用於檢查即將提交的快照,例如,檢查是否有所遺漏,確保測試執行,以及核查程式碼。如果該鉤子以非零值退出,Git 將放棄此次提交,不過你可以用 git commit --no -verify 來繞過這個環節。你可以利用該鉤子,來檢查程式碼風格是否一致(執行類似 lint 的程式)、尾隨空白字元是否存在(自帶的鉤子就是這麼做的),或新方法的文件是否適當。 -
post-commit
鉤子在整個提交過程完成後執行。它不接收任何引數,但你可以很容易地通過執行git log -1 HEAD
來獲得最後一次的提交資訊。該鉤子一般用於通知之類的事情。
二十三、Git 物件
-
可以通過底層命令
hash-object
來演示上述效果——該命令可 將任意資料保存於 .git 目錄,並返回相應的鍵值。 -
-w
選項指示hash-object
命令儲存資料物件;若不指定此選項,則該命令僅返回對應的鍵值。--stdin
選項則指示該命令從標準輸入讀取內容;若不指定此選項,則須在命令尾部給出待儲存檔案的路徑。 -
可以通過
cat-file
命令從 Git 那裡取回資料。這個命令簡直就是一把剖析 Git 物件的瑞士軍刀。為cat-file
指定-p
選項可指示該命令自動判斷內容的型別,併為我們顯示格式友好的內容 -
利用
cat-file -t
命令,可以讓 Git 告訴我們其內部儲存的任何物件型別,只要給定該物件的 SHA-1 值
樹物件
-
所有內容均以樹物件和資料物件的形式儲存,其中樹物件對應了 UNIX 中的目錄項,資料物件則大致上對應了 inodes 或檔案內容。一個樹物件包含了一條或多條樹物件記錄(tree entry),每條記錄含有一個指向資料物件或者子樹物件的 SHA-1 指標,以及相應的模式、型別、檔名資訊。
-
master^{tree}
語法表示 master 分支上最新的提交所指向的樹物件。 -
Git 根據某一時刻暫存區(即 index 區域,下同)所表示的狀態建立並記錄一個對應的樹物件,如此重複便可依次記錄(某個時間段內)一系列的樹物件。
可以通過底層命令
update-index
為一個單獨檔案建立一個暫存區。必須為上述命令指定
--add
選項,因為此前該檔案並不在暫存區中同樣必需的還有
--cacheinfo
選項,因為將要新增的檔案位於 Git 資料庫中,而不是位於當前目錄下。 -
檔案模式有 100644,表明這是一個普通檔案;100755,表示一個可執行檔案;120000,表示一個符號連結。
-
可以通過
write-tree
命令將暫存區內容寫入一個樹物件。此處無需指定-w
選項——如果某個樹物件此 前並不存在的話,當呼叫write-tree
命令時,它會根據當前暫存區狀態自動建立一個新的樹物件 -
通過呼叫
read-tree
命令,可以把樹物件讀入暫存區。本例中,可以通過對read-tree
指定--prefix
選項,將一個已有的樹物件作為子樹讀入暫存區 -
如果基於這個新的樹物件建立一個工作目錄,你會發現工作目錄的根目錄包含兩個檔案以及一個名為 bak 的子目錄,該子目錄包含 test.txt 檔案的第一個版本
提交物件
-
這三個提交物件分別指向之前建立的三個樹物件快照中的一個。現在,如果對最後一個提交的 SHA-1 值執行 git log 命令,會出乎意料的發現,你已有一個貨真價實的、可由 git log 檢視的 Git 提交歷史了
-
這就是每次我們執行 git add 和 git commit 命令時, Git 所做的實質工作——將被改寫的檔案儲存為資料物件,更新暫存區,記錄樹物件,最後建立一個指明瞭頂層樹物件和父提交的提交物件。
這三種主要的 Git 物件——資料物件、樹物件、提交物件——最初均以單獨檔案的形式儲存在 .git/objects 目錄下。
二十四、Git 引用
-
這基本就是 Git 分支的本質:一個指向某一系列提交之首的指標或引用。
-
當執行類似於 git branch (branchname) 這樣的命令時,Git 實際上會執行 update-ref 命令,取得當前 所在分支最新提交對應的 SHA-1 值,並將其加入你想要建立的任何新引用中。
HEAD 引用
-
HEAD 檔案是一個符號引用(symbolic reference),指向目前所在的分支。所謂符號引用,意味著它並不像普通引用那樣包含一個 SHA-1 值——它是一個指向其他引用的指標。
-
當我們執行
git commit
時,該命令會建立一個提交物件,並用 HEAD 檔案中那個引用所指向的 SHA-1 值設定其父提交欄位。
二十五、維護與資料恢復
資料恢復
-
最方便最常用的方法,是使用一個名叫 git reflog 的工具。當你正在工作時,Git 會默默地記錄每一次你改變 HEAD 時它的值。每一次你提交或改變分支,引用日誌都會被更新。引用日誌(
reflog
)也可以通過git update-ref
命令更新。 -
由於引用日誌資料存放在
.git/logs/
目錄中,現在你已經沒有引用日誌了。這時該如何恢復那次提交?一種方式是使用git fsck
實用工具,將會檢查資料庫的完整性。如果使用一個--full
選項執行它,它會向你顯示出所有沒有被其他物件指向的物件
移除物件
-
警告:移除物件的操作對提交歷史的修改是破壞性的。它會從你必須修改或移除一個大檔案引用最早的樹物件開始重寫 每一次提交。如果你在匯入倉庫後,在任何人開始基於這些提交工作前執行這個操作,那麼將不會有任何問題 否則,你必須通知所有的貢獻者他們需要將他們的成果變基到你的新提交上。
-
執行 gc 來檢視資料庫佔用了多少空間,也可以執行
count-objects
命令來快速的檢視佔用空間大小。假設你不知道該如何找出哪個檔案或哪些檔案佔用瞭如此多的空間。如果你執行
git gc
命令,所有的物件將被放入一個包檔案中。你可以通過執行
git verify-pack
命令,然後對輸出內容的第三列(即檔案大小)進行排序,從而找出這個大檔案。你也可以將這個命令的執行結果通過管道傳送給
tail
命令,因為你只需要找到列在最後的幾個大物件。為了找出具體是哪個檔案,可以使用
revlist
命令,我們在 指定特殊的提交資訊格式 中曾提到過。如果你傳遞--objects
引數給rev-list
命令, 它就會列出所有提交的 SHA-1、資料物件的 SHA-1 和與它們相關聯的檔案路徑。可以使用以下命令來找出你的資料物件的名字 -
你必須重寫 7b30847 提交之後的所有提交來從 Git 歷史中完全移除這個檔案。為了執行這個操作,我們 要使用
filter-branch
命令。$ git filter-branch --index-filter \ 'git rm --ignore-unmatch --cached git.tgz' -- 7b30847^.. Rewrite 7b30847d080183a1ab7d18fb202473b3096e9f34 (1/2)rm 'git.tgz' Rewrite dadf7258d699da2c8d89b09ef6670edb7d5f91b4 (2/2) Ref 'refs/heads/master' was rewritten
--index-filter
選項類似於在 重寫歷史 中提到的的--tree-filter
選項,不過這個選項並不會讓命令將修改在硬碟上檢出的檔案,而只是修改在暫存區或索引中的檔案。你必須使用
git rm --cached
命令來移除檔案,而不是通過類似rm file
的命令 - 因為你需要從索引中移除它,而不是磁碟中。還有一個原因是速度 - Git 在執行過濾器時,並不會檢出每個修訂版本到磁碟中,所以這個過程會非常快。如果願意的話,你也可以通過
--tree-filter
選項來完成同樣的任務。git rm
命令的--ignore-unmatch
選項告訴命令:如果嘗試刪除的模式不存在時,不提示錯誤。最後,使用
filter-branch
選項來重寫自 7b30847 提交以來的歷史,也就是這個問題產生的地方。否則,這個命令會從最舊的提交開始,這將會花費許多不必要的時間。 -
你的歷史中將不再包含對那個檔案的引用。不過,你的引用日誌和你在
.git/refs/original
通過filter-branch
選項新增的新引用中還存有對這個檔案的引用,所以你必須移除它們然後重新打包資料庫。在重新打包前需要移除任何包含指向那些舊提交的指標的檔案……