git常用命令總結

       Git常用命令總

Git

總結Git常用的命令與概念, 包含分支, 子模塊等。

Git簡介

Git誕生

Git的誕生與Linux密切相關.

Linus在1991年創建了開源的Linux, 並且有着爲數衆多的參與者. 在1991-2002年間, 所有的源代碼都由Linus手工合併,因爲Linus堅定的反對CVS和SVN, 這些集中式的版本控制系統不但速度慢, 而且必須聯網才能使用.

到了2002年, 因爲Linux的代碼庫已經龐大到難以繼續通過手工方式管理, 於是整個項目組啓用了一個商業版本的分佈式版本控制系統BitKeeper來管理和維護代碼.開發BitKeeper的 BitMover公司授權Linux社區免費使用. 但在2005年, 由於開發 Samba的Andrew試圖破解BitKeeper的協議而被發現, 於是BitMover公司收回了免費使用權.這迫使Linux開源社區不得不吸取教訓, 只有開發一套屬於自己的版本控制系統才能避免重蹈覆轍.

他們對新的版本控制系統制訂了若干目標: 速度, 簡單的設計, 對非線性開發模式的強力支持(允許上千個並行開發的分支), 完全分佈式, 有能力高效管理類似Linux內核一樣的超大規模項目.自2005年誕生以來, Git日臻成熟完善, 2008年使用Git存儲的Github網站上線, 無數開源項目遷移至Github, 包括jQeury, PHP, Ruby等.

版本控制系統

版本控制系統可以分爲集中式版本控制系統(CVS及SVN), 分佈式版本控制系統(Git).

集中式版本控制系統

在集中式的版本控制系統中, 版本庫是集中存放在中央服務器的. 工作時先從中央服務器獲取最新的版本, 然後開始工作, 工作完成後再將修訂推送給中央服務器.在這類系統中, 都有一個單一的集中管理的服務器, 保存所有文件的修訂版本, 而協同工作的人員都通過客戶端連接到這臺服務器, 獲取最新的文件或者提交更新.

分佈式版本控制系統

與集中式版本控制系統相比, 分佈式版本控制系統沒有中央服務器, 每個人的電腦上都是一個完整的版本庫. 這樣不需聯網就能工作, 而如果需要多人協作,只需把各自的修改推送給對方即可.

分佈式版本控制系統的安全性更高, 因爲完整的版本庫不只一份, 不用擔心版本庫丟失.

在實際使用分佈式版本控制系統時, 通常也有一臺類似與中央服務器的電腦, 但它的作用僅僅是用來方便交換大家的修改.

基本用法

Git初始化

在使用 git 管理代碼之前, 首先需要對Git進行初始化.

Git配置

使用Git之前需要設置你的名字和email, 這些就是你提交commit時的簽名, 每次提交記錄都會包含這些信息. 可以使用 git config 命令進行配置.

1
2
git config --global user.name "xxx"
git config --global user.email "xxx"

執行上述命令之後, 會在用戶家目錄創建一個 .gitconfig 文件,該文件既是Git的全局配置.

如果你想在某個項目中使用其他的用戶配置, 可在使用 git config 命令時不帶 --global 選項, 這會在當前項目下創建 .git/config, 從而使用針對當前項目的配置.

獲得一個Git倉庫

有兩種方式可以初始化一個Git倉庫, 一種是從已有的Git倉庫中clone, 另一種是新建一個倉庫, 然後進行版本控制.

Clone一個倉庫

爲獲得一個項目的copy, 我們需要知道這個項目倉庫的地址(Git URL). Git可以在許多協議下使用, 所以URL可以是ssh, http(s), git等.

可以使用以下代碼進行clone處理:

1
git clone http://xxx/projectname

clone操作完成後, 會發現當前目錄下多了一個 projectname 文件夾, 這個文件夾中的內容就是clone下來的代碼.

初始化一個新的倉庫

對於一個已經存在的目錄可以使用初始化的方式使它被Git管理:

1
2
cd projectname
git init

使用以上命令, Git會輸出:

1
Initialized empty Git repository in xxx

Git會在該目錄下創建一個名叫 .git 的目錄, 這意味着一個Git倉庫被初始化成功.

一般工作流程

使用Git進行版本管理的一般流程如下:

  1. 創建或修改文件
  2. 使用 git add 命令添加新創建或修改的文件到本地緩存區(Index)
  3. 使用 git commit 命令提交到本地代碼庫
  4. 使用 git push 命令將本地代碼庫同步到遠端代碼庫(可選)

下面對每個步驟進行講解:

首先進行工程目錄, 創建新文件:

1
2
cd projectname
touch file1 file2 file3

修改文件內容:

1
2
3
echo "testcontent1" >> file1
echo "testcontent2" >> file2
echo "testcontent3" >> file3

使用 git status 查看當前Git倉庫狀態:

1
git status

可以發現, 新增的3個文件處於 untracked 狀態, 將文件加入到緩存區:

1
git add file1 file2 file3

再次執行 git status 命令, 會發現新增文件已經能夠commit.

可以使用 git diff 命令查看緩存區中哪些文件被修改:

1
git diff --cached

如果沒有 --cached 參數, 則會顯示當前沒有加入到緩存區中的修改.

當所有的修改到加入到緩存區之後, 進行提交:

1
git commit -m "add 3 files"

該命令需要使用 -m 參數添加本次修改的註釋, 完成後會記錄一個新的項目版本. 也可以使用 -a 參數將所有沒有加入到緩存區的修改一起提交, 但該參數不會添加新創建的文件.

需要注意, 如果是修改/添加文件, 要使用 git add 命令添加到緩存區纔會提交; 如果是刪除文件, 則使用 git rm 命令會自動將已刪除的文件信息添加到緩存區.

最後, 可以使用 git push 命令將本地倉庫同步到遠端服務器.

1
git push origin master

分支與合併

Git的分支功能可以讓你在主線(master)分支之外進行代碼提交, 同時又不會影響代碼庫主線. 分支的作用體現在多人協作開發中, 例如在一個團隊開發中, 如果一個功能需要一個月完成, 就可以新建一個分支, 只把該功能的代碼提交到這個分支中, 而其他功能可以繼續使用主線開發, 這兩者之間的提交不會互相影響. 當功能完成後, 只需要將分支合併到主線即可.

分支

一個Git倉庫可以維護很多開發分支. 使用以下代碼可以創建分支:

1
git branch branchname

如果不帶分支名, 即查看當前的分支列表, 以及當前處於哪個分支.

使用 git checkout 命令可以切換到其他分支:

1
git checkout branchname

使用 git merge 命令進行分支合併, 將 branchname 分支合併到主分支:

1
2
git checkout master
git merge -m "xxx" branchname

如果兩個branch修改了相同的文件, 則合併時可能發生衝突導致合併失敗.失敗之後可以先使用 git status 查看狀態, 例如都修改了 file3, 則會看到file3 顯示爲both modified, 可以直接查看 file3的內容(或使用 git diff), 衝突的內容可以直接進行編輯以去掉衝突.之後再提交到master分支即可.

當完成合並之後, 新分支如果不再需要, 可以使用以下命令刪除:

1
git branch -d branchname

注意: 該命令只會刪除已被當前分支合併的分支, 如果需要強制刪除則使用 -D 參數.

撤銷一個合併

如果合併後導致代碼混亂, 可以使用以下命令放棄當前修改, 回到合併之前的狀態:

1
git reset --hard HEAD^

快速向前合併

通常, 一個合併會產生一個合併提交, 把兩個分支的每一行內容都合並進來.

但是, 如果當前分支的每一個提交都已經存在另一個分支中(沒有內容上的差異), 則Git會執行一個快速向前操作, 不創建任何新提交, 只是將當前分支指向合並進來的分支.

Git日誌

查看日誌

使用 git log 命令顯示所有的提交:

1
git log

如果提交的歷史記錄很長, 回車會逐步顯示, 輸入q可以退出.

日誌統計

如果在 git log 命令中使用 --stat 參數, 會顯示在每個提交中哪些文件被修改了, 分別添加或刪除了多少行內容. 相當於打印詳細的提交記錄.

格式化日誌

使用 --pretty 參數來確定顯示格式, 例如oneline/short/medium/full/fuller/email/raw等.

1
git log --pretty=oneline

如果以上參數都不符合你的要求, 也可以使用 --pretty=format 參數定義格式.

--graph 選項可以可視化你的提交圖, 會用ASCII字符來畫出一個很漂亮的提交歷史線.

日誌排序

日誌記錄可以按照一個指定的特定順序顯示, 只需要添加順序參數.

在默認情況下, 提交會按照逆時間順序顯示, 可以指定 --topo-order 參數, 讓提交按照拓撲順序顯示(即子提交在父提交前顯示).

1
git log --pretty=format:"%h: %s" --topo-order --graph

也可以使用 --reverse 參數來逆向顯示所有提交日誌.

比較內容

比較提交

首先對項目文件進行一些修改:

1
2
3
cd projectname
echo "new line" >> README.md
echo "new file" >> file1

然後使用 git diff 命令比較修改的或提交的文件內容:

1
git diff

該命令輸出當前工作目錄中修改的內容, 並不包含新加的文件, 而且這些內容還沒有添加到本地緩存區.

如果需要查看緩存區與上次提交之間的差別, 需要使用 --cached 參數.

比較分支

使用 git diff 也可以比較項目中任意兩個分支的差異.首先使用以下命令創建一個新的分支, 並進行一些修改:

1
2
3
4
5
6
git branch test
git checkout test
echo "branch test" >> file1
echo "new file2" >> file2
git add *
git commit -m "update test branch"

使用以下命令可以查看 test 分支與 master 分支的差異:

1
git diff master test

更多的比較選項

可以使用以下命令查看當前工作目錄與另一個分支的差異:

1
2
git checkout master
git diff test

也可以加上路徑限定符以只比較某個文件或目錄:

1
git diff test file1

使用 --stat 參數可以統計有哪些文件被改動, 以及改動的行數.

分佈式的工作流程

分佈式的工作流程

假設當前項目在 /tmp/gitproject 目錄下, 這時有另一個用戶想協作開發.

首先, 該用戶需要對Git倉庫進行克隆:

1
2
cd /tmp
git clone /tmp/gitproject myrepo

這樣就新建了一個叫 myrepo 的目錄, 該目錄包含一份 girproject 倉庫的一模一樣的克隆, 並且擁有原始項目的歷史記錄.

在 myrepo 中進行一些修改並提交:

1
2
3
4
cd myrepo
echo "newcontent" > newfile
git add newfile
git commit -m "add newfile"

修改提交之後, 原 gitproject 倉庫想合併這份修改可以使用以下命令:

1
2
cd /tmp/gitproject
git pull /tmp/myrepo master

這樣就把 myrepo 的主分支合併到了 gitproject 的當前分支中, 如果兩個分支同時修改了同一個文件, 可能需要手工修復衝突.

如果經常需要操縱遠程分支, 可以定義遠程分支的縮寫:

1
git remote add myrepo /tmp/myrepo

git pull 命令執行兩個操作: 從遠程分支抓取 git fetch 的內容, 然後把它合併git merge 到當前分支.

在 gitproject 中可以用 git fetch 來執行抓取, 然後用 git log 查看遠程分支的所有修改, 然後進行合併:

1
2
3
git fetch myrepo
git log -p master ..myrepo/master
git merge myrepo/master

如果在 myrepo 目錄下執行 git pull, 則相當於把 gitproject 上的修改同步到本地.

因爲 myrepo 是從 gitproject 倉庫克隆的, 所以不需要指定 gitporject 的地址. Git 把 gitproject 倉庫的地址存儲到 myrepo 的配置文件中, 即git pull 時默認使用的遠程倉庫:

1
git config --get remote.origin.url

如果兩個倉庫在不同的主機, 可以使用 ssh 協議進行 clone 和 pull 的操作.

公共Git倉庫

在開發過程中, 通常會使用一個公共的倉庫, 並clone到自己的開發環境中, 完成一個階段的代碼後會告訴目標倉庫的維護者來 pull 自己的代碼.

1
2
3
4
git clone /path/to/repo
git pull /path/to/other/repo
git clone ssh://host/repo

將修改推送到一個公共倉庫

通過 http 或者 git 協議, 其他維護者可以通過遠程訪問的方式抓取你最近的修改, 但是他們沒有些權限.

可以使用 git push 命令將本地的修改推送到遠程Git倉庫:

1
2
git push ssh://host/repo master:master
git push ssh://host/repo master

該命令的目的倉庫可以是 ssh 或 http/https 協議訪問.

當推送失敗時如何處理

如果推送不是快速向前的, 可能會出錯. 這種情況通常是因爲沒有使用 git pull 獲取遠程倉庫的最新更新, 在本地修改的同時遠程倉庫已經發生變化, 此時應該先使用git pull 合併最新的修改後再次執行 git push 命令.

Git標籤

輕量級標籤

可以使用 git tag 不帶任何參數創建一個標籤指定某個提交:

1
2
3
4
cd /tmp/gitproject
git log
git tag stable-1 8c2333
git tag

這樣之後, 我們可以使用 stable-1 作爲提交ID(8c2333)的代稱.

標籤對象

如果想爲一個tag添加註釋或者簽名, 就需要創建一個標籤對象.

git tag 使用 -a, -s 或 -u 三個參數的任意一個都會創建一個標籤對象, 並且需要一個標籤消息來爲 tag 添加註釋. 如果沒有 -m 或 -F 參數, 該命令執行時會啓動一個編輯器來讓用戶輸入標籤消息.

1
2
git tag -a stable-2 8c2333 -m "stable 2"
git tag

當這樣的一條命令執行之後, 一個新的對象被添加到Git對象庫中, 並且標籤引用指向一個標籤對象而不再是指向一個提交. 這就是標籤對象與輕量級標籤的區別.

簽名的標籤

簽名標籤可以讓提交和標籤更加完整. 如果配 GPG key , 則很容易創建簽名的標籤.

可以在 .git/config 或 ~/.gitconfig 中配置key:

1
2
[user]
signingkey = <gpg-key-id>

或者使用命令行配置:

1
git config (--global) user.signingkey <gpg-key-id>

可以在創建標籤時使用 -s 參數來創建簽名的標籤, 且如果在配置文件中沒有key, 使用 -u 參數可以直接指定:

1
2
git tag -s stable-1 1b2
git tag -u <gpg-key-id> stable-1 1b2

中級用法

忽略文件

在項目中經常會生成一些Git系統不需要追蹤的文件. 典型的是在編譯過程中產生的文件或是編輯器生成的臨時備份文件. 當然, 若你不需要追蹤這些文件, 可以在平時不使用git add 去把它們加到索引中. 但這樣有一些缺點:

  1. 項目中到處都有未追蹤的文件
  2. 在這種情況下 git add . 以及 git commit -a 命令就不能被使用
  3. git status 命令還會輸出這些文件沒有被追蹤.

比較好的方式是在頂層工作目錄中添加一個名爲 .gitignore 的文件, 來定義使Git系統忽略掉哪些文件. 該文件的內容中, 以# 開始的行爲註釋. 一個內容示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 忽略某個文件
foo.txt
# 忽略所有的 html 文件
*.html
# 在上面的基礎上, 追蹤 foo.html 文件
!foo.html
# 忽略所有的 .o 和 .a 文件.
*.[oa]
# 忽略整個文件夾
foo/
# 追蹤文件夾中的 foo1.txt
!foo/foo1.txt

rebase

rebase

假設你現在的分支基於遠程分支 origin , 創建一個名爲 mywork 的分支.

1
git checkout -b mywork origin

現在在這個分支上做一些修改, 然後生成兩個提交:

1
2
3
4
vi file.txt
git commit
vi otherfile.txt
git commit

但與此同時, 有些人也在 origin 分支上做了一些修改並做了提交. 這意味着 origin 與 mywork 分支各自前進了, 它們之間分叉了. 在這裏, 你可以利用 pull 命令把 origin 分支上的修改拉取下來並和你的修改合併, 其結果就像一個新的合併的提交. 但是如果你想讓 mywork 分支歷史看起來像沒有經過任何合併一樣, 可以使用git rebase.

1
2
git checkout mywork
git rebase origin

這些命令會將你的 mywork 分支裏的每個提交取消掉, 並且把它們臨時保存爲補丁(在 .git/rebase 目錄中), 然後把 mywork 分支更新到最新的 origin 分支, 然後把補丁應用到 mywork 分支上. 當 mywork 分支更新之後, 它會指向這些新創建的提交, 而把老的提交丟棄掉. 如果運行垃圾收集命令, 這些提交就會被刪除. 在rebase過程中允許出現衝突, 在這種情況下, Git會停止rebase 並讓你去解決這些衝突, 在解決完衝突後, 用git add 命令去更新這些內容的索引, 之後並不需要執行 commit, 只需要執行以下命令:

1
git rebase --continue

這樣Git會繼續應用餘下的補丁. 在任何時候, 都可以使用 --abort 參數來終止rebase過程, 並且 mywork 分支會恢復到 rebase 開始前的狀態.

交互式rebase

rebase還有一種交互式使用方式, 這種方式通常用於在向別處推送提交之前對它們進行重寫. 交互式rebase提供了一個簡單易用的途徑讓你在和別人分享提交之前對你的提交進行分割、合併或者重排序. 在把從其他開發者處拉取的提交應用到本地時, 也可以使用交互式rebase對它們進行清理. 如果你想在rebase的過程中對一部分提交進行修改, 可以在git rebase 命令中加入 -i 或者 --interactive 參數去調用交互模式.

1
git rebase -i origin/master

這個命令會進行交互式rebase操作, 操作對象是那些自最後一次從origin倉庫拉取或者向origin推送之後的所有提交. 若想查看將被rebase的提交, 可以使用如下的log命令:

1
git log github/master..

一旦你完成對提交信息的編輯並退出編輯器, 這個新的提交及提交信息會被保存起來. 如果指定進行edit操作, git會完成同樣的工作, 但在對下一個提交進行操作之前, 它會返回到命令行讓你對提交進行修正, 或者對提交內容進行修改. 例如你想要分割一個提交, 你需要對那個提交指定edit操作: 這個命令會進入命令行, 撤銷該提交, 然後創建兩個或者更多個提交. 假設提交21d80a5修改了兩個文件file1和file2, 你想把這兩個修改放到不同的提交裏. 使用命令行操作如下:

1
2
3
4
5
6
git reset HEAD
git add file1
git commit -m "first part of split commit"
git add file2
git commit -m "second part of split commit"
git rebase --continue

交互式rebase的最後一個作用是丟棄提交. 如果把一行刪除而不是指定picksquashedit 中的任何一個, Git會從歷史中移除該提交.

交互式添加

交互式添加提供友好的界面去操作Git索引, 同時也提供了可視化索引的能力. 只需簡單鍵入 git add -i, 即可使用此功能. Git會列出所有修改過的文件及它們的狀態.

1
git add -i

在這個例子中,我們可以看到有5個修改過的文件還沒有被加入到索引中,甚至可以看到每個文件增加和減少的行數。緊接着是一個交互式的菜單,列出了我們可以在此模式中使用的命令. 如果我們想要暫存這些文件,我們可以鍵入'2'或者'u'進入更新模式. 然後我們可以通過鍵入文件的範圍(本例中是1-4)來決定把哪些文件加入到索引之中.

如果鍵入回車,我會回到主菜單中,同時可以看到那些指定文件的狀態已經發生了改變:

現在我們可以看到前4個文件已經被暫存,但是最後一個沒有. 基本上,這是一個更加緊湊的查看狀態的方式,實質上的信息與我們在命令行中運行git status是一致的.

儲藏

儲藏

當你在做一項複雜的工作時, 發現了一個和當前工作不相關但是又很討厭的BUG, 你這時想先修復BUG再做手頭的工作, 那麼就可以使用 git stash 來保存當前的工作狀態, 等你修復完BUG後再執行'反儲藏'操作就可以回到之前的工作裏.

1
git stash save "work in progress for foo feature"

上面這條命令會保存你的本地修改到儲藏中, 然後將你的工作目錄和索引裏的內容全部重置, 回到你當前所在分支的上次提交時的狀態. 修改BUG之後進行提交:

1
git commit -a -m "blorpl: typofix"

當你修復完成BUG後, 可以使用以下命令來恢復到以前的工作狀態:

1
git stash apply

儲藏隊列

可以多次使用 git stash 命令, 每執行一次就會把針對當前修改的儲藏添加到儲藏隊列中. 用 git stash list 命令可以查看所有的儲藏:

1
git stash list

也可以用以下命令來恢復隊列中的任意一個儲藏:

1
git stash apply stash@{1}

使用以下命令清空隊列:

1
git stash clear

Git樹名

Git樹名

不使用40個字節長的SHA串來表示一個提交或是其它Git對象, 有很多種其它的名字表示方法. 在Git中, 這些名字就叫做樹名(treeish).

Sha短名

如果你的一個提交的SHA名字是 980e3ccdaac54a0d4de358f3fe5d718027d96aae, Git會把下面的串視爲等價:

1
2
3
980e3ccdaac54a0d4de358f3fe5d718027d96aae
980e3ccdaac54a0d4
980e3cc

只需要你的SHA短名是不重複的, 它就不會和其它名字衝突(5個字節以上很難重複), Git也會把SHA短名自動補全.

分支,Remote或標籤

可以使用分支, remote或標籤名來代替SHA串名, 它們只是指向某個對象的指針. 假設你的master分支目前在提交 980e3 上, 現在把它推送到origin上並把它命名爲標籤 v1.0, 那麼下面的串會被Git視爲等價:

1
2
3
4
5
6
7
980e3ccdaac54a0d4de358f3fe5d718027d96aae
origin/master
refs/remotes/origin/master
master
refs/heads/master
v1.0
refs/tags/v1.0

日期標識符

Git的引用日誌可以讓你做一些相對查詢操作:

1
2
master@{yesterday}
master@{1 month ago}:

上面第一條命令的意思是: master分支的昨天狀態.

注意: 即使是在兩個有相同master分支的倉庫上執行這條命令, 但是如果這兩個倉庫在不同機器上, 那麼執行結果也很可能不一樣.

順序標識符

用以下命令表達某點前面的第N個提交:

1
master@{5}

意思是master前面的第5個提交.

多個父對象

某個提交的第N個直接父提交. 這種格式在合併提交時特別有用, 這樣就可以使提交對象有多於一個直接父對象.

1
master^2

波浪號

波浪號是用來標識一個提交對象的第N級父對象:

1
master~2

以上代表master所指向的提交對象的第一個父對象的第一個父對象. 等價於以下表達式:

1
master^^

可以將這些標識符疊加起來, 下面3個表達式指向同一個提交:

1
2
3
master^^^^^^
master~3^~2
master~6

樹對象指針

每一個提交對象是指向一個樹對象的, 假如你要得到一個提交對象指向的樹對象的SHA串名, 可以使用以下方式:

1
master^{tree}

二進制標識符

如果你需要某個二進制對象的SHA串名, 你可以在樹名後添加二進制對象對應的文件路徑來得到:

1
master:/path/to/file

區間

可以用'..'來指兩個提交之前的區間, 下面的命令會給出在 '7b593b5' 和 '51bea1' 之間除了 '7b593b5' 的所有提交:

1
7b593b5..51bea1

以下命令包括從 7b593b 開始的提交, 兩個命令是等價的:

1
2
7b593b..
7b593b..HEAD

追蹤分支

在Git中追蹤分支是用於聯繫本地分支和遠程分支的. 如果你在追蹤分支上執行推送或者拉取時, 它會自動推送或拉取到關聯的遠程分支上. 如果你經常要從遠程倉庫里拉取分支到本地, 並且不想麻煩的使用git pull 這種格式, 那麼就應當使用追蹤分支功能. git clone 命令會自動在本地建立一個master分支, 它是 origin/master 的追蹤分支. 而 origin/master 就是被克隆倉庫的 master 分支. 你可以在使用git branch 命令時加上 --track 參數來手動建立一個追蹤分支.

1
git branch --track experimental origin/experimental

當你運行以下命令時:

1
git pull experimental

它會自動從 origin 抓取內容, 再把遠程的 origin/experimental 分支合併到本地的 experimental 分支. 當要把修改推送到 origin 時, 它會將你本地的 experimental 分支中的修改推送到 origin/experimental 分支裏而無需指定源.

使用 Git Grep 進行搜索

git grep 命令查找Git倉庫裏的某段文字是很方便的. 當然, 你也可以使用 unix 下的 grep 命令進行搜索, 但是git grep 命令可以讓你不用檢出歷史文件就能查找它們.

例如, 你需要查看Git倉庫中使用 xmmap 函數的地方, 可以使用如下命令:

1
git grep xmmap

還可以使用如下參數:

  1. -n: 顯示行號
  2. --name-only: 只顯示文件名
  3. -c: 查看每個文件有都少匹配的行

如果想要查找Git倉庫中某個特定版本的內容, 在命令末尾加上標籤名即可:

1
git grep xmmap v1.5.0

還可以組合一些搜索條件, 例如查看在倉庫的哪個地方定義了 SORT_DIRENT:

1
git grep -e "#define" --and -e SORT_DIRENT

除了與條件搜索, 也可以進行或條件搜索:

1
git grep --all-match -e '#define" -e SORT_DIRENT

也可以查找符合一個條件且符合兩個條件之一的文件行. 例如查找名字中含有 PATH 或 MAX 的常量定義:

1
git grep -e '#define' --and \(-e PATH -e MAX\)

Git的撤銷操作-重置, 檢出和撤銷

修復未提交文件中的錯誤

如果你的工作目錄中很混亂, 而且還沒有進行提交. 則可以使用以下命令讓工作目錄回到上次提交時的狀態:

1
git reset --hard HEAD

該命令會清空所有的未提交內容(不包括未跟蹤文件).

如果只需要恢復一個文件, 可以使用如下命令:

1
git checkout -- hello.rb

該命令會將 hello.rb 從 HEAD 中檢出並恢復成未修改的樣子.

修復已提交文件中的錯誤

如果你已經做了一個提交, 但是又想對提交進行修改. 有以下兩種方式:

一是創建一個新的提交, 在新的提交中撤銷老提交所做的修改. 這種做法適用於代碼已發佈的情況.

二是去修改老提交.

以下命令演示瞭如果撤銷最近的一個提交:

1
git revert HEAD

該命令創建了一個撤銷上次提交的新提交. 這樣就可以修改新提交裏的註釋等信息, 也可以撤銷更早期的修改, 例如撤銷上上次提交:

1
git revert HEAD^

在這種情況下, Git會嘗試去撤銷老提交, 然後留下完整的老提交之前的版本. 如果你最近的修改和要撤銷的修改有衝突, 需要手工解決.

注意: git revert 命令並不會直接創建一個提交, 你需要手動提交一次. git commit 現在支持一個--amend 參數, 通過這個參數可以修改剛纔的提交.

維護Git

保證良好的性能

在大的倉庫中, Git靠壓縮歷史信息來節約磁盤和內存空間. 壓縮操作不是自動進行的, 需要使用以下命令手動執行:

1
git gc

壓縮操作比較耗時.

保持可靠性

可以使用 git fsck 運行一些倉庫的一致性檢查.

1
git fsck

該操作也比較耗時, 通常的警告信息是 懸空對象. 懸空對象並不是問題, 最壞的情況只是它們多佔了一些磁盤空間. 有時它們是找回丟失工作的最後一絲希望.

建立公共倉庫

建立一個公共倉庫

假設你個人的倉庫在目錄 ~/proj. 我們先克隆一個新的裸倉庫, 並且創建一個標誌文件告訴 git-daemon 這是個公共倉庫:

1
2
git clone --bae ~/proj proj.git
touch proj.git/git-daemon-export-ok

上面的命令創建了一個proj.git目錄, 這個目錄裏有一個裸倉庫(即只有.git目錄裏的內容, 沒有任何檢出的文件). 下一步就是把這個 proj.git 目錄拷貝到用來託管公共倉庫的主機上.

通過git協議導出git倉庫

推薦的做法是用git協議導出git倉庫. 你需要啓動git daemon, 它會在9418端口監聽, 默認情況會允許你訪問所有的git目錄(看目錄中是否有git-daemon-export-ok文件). 如果以某些目錄作爲git-daemon 的參數, 那麼它會限制用戶通過git協議只能訪問這些目錄. 可以在 inetd service 模式下運行git-daemon.

通過http協議導出git倉庫

git協議有不錯的性能和可靠性, 但如果主機上已經配置好一個web服務器, 使用http協議可能會更容易. 你需要把新建的裸倉庫放到web服務器可以訪問的目錄裏, 同時做一些調整以便web客戶端獲得它們所需的額外信息:

1
2
3
4
mv proj.git /path/html/proj.git
cd proj.git
git --bare update-server-info
chmod a+x hooks/post-update

然後通過以下命令克隆倉庫即可:

1
git clone https://yourserver.com/~you/proj.git

建立一個私有倉庫

通過SSH協議訪問私有倉庫

一般來說最簡單的辦法是通過SSH協議訪問Git. 如果你在一臺機器上有一個SSH帳號, 你只需要把Git裸倉庫放到任何一個可以通過SSH訪問的目錄, 然後可以像SSH登錄一樣簡單的使用它. 假設你現在有一個倉庫, 並且你要把它建成可以在網上訪問的私有倉庫. 你可以使用下面的命令導出一個裸倉庫, 然後用SCP命令把它們拷貝到服務器:

1
2
git clone --bare /home/user/myrepo/.git /tmp/myrepo.git
scp -r /tmp/myrepo.git myserver.com:/opt/git/myrepo.git

如果其它人也在 myserver.com 這臺服務器上有SSH帳號, 那麼他可以從這臺服務器上克隆代碼:

1
git clone myserver.com:/opt/git/myrepo.git

上面的命令會提示你輸入SSH密碼或是使用公鑰.

高級用法

創建新的空分支

在有些情況下, 你可能會想要保留那些與你的代碼沒有共同祖先的分支. 例如在這些分支上保留生成的文檔或者其他一些東西. 如果你需要創建一個不實用當前代碼庫作爲父提交的分支, 方法如下:

1
2
3
4
5
6
git symbolic-ref HEAD refs/heads/newbranch
rm .git/index
git clean -fdx
<do work>
git add your file
git commit -m "Initial commit"

修改歷史

交互式衍合是修改單個提交的好方法. git filter-branch 是修改大量提交的好方法.

高級分支和合並

在合併過程中得到解決衝突的協助

git會把所有何以自動合併的修改加入到索引中, 所以 git diff 只會顯示有衝突的部分.

在我們解決衝突之後, 得到的提交會有兩個而不是一個父提交: 一個父提交會成爲HEAD, 也就是當前分支的tip; 另一個父提交會成爲另一個分支的tip, 暫時保存在MERGE_HEAD中. 在合併過程中, 索引中保存着每個文件的三個版本. 三個文件暫存中的每一個都代表了文件的不同版本:

1
2
3
git show :1:file.txt
git show :2:file.txt
git show :3:file.txt

當使用 git diff 去顯示衝突時, 它在工作樹, 暫存2和暫存3之間執行三路diff操作, 只顯示那些兩方都有的塊(即當一個塊的合併結果只從暫存2中得到時是不會被顯示的).

Git不在每行前面加上單個+或者-, 而是使用兩欄去顯示差異: 第一欄用於顯示第一個父提交與工作目錄文件拷貝的差異; 第二欄用戶顯示第二個父提交與工作文件拷貝的差異. 解決衝突之後顯示示例如下:

1
2
3
4
5
6
7
8
9
git diff
diff --cc file.txt
index 802992c,2b60207..0000000
--- a/file.txt
+++ b/file.txt
@@@ -1,1 -1,1 +1,1 @@@
- Hello world
-Goodbye
++Goodbye world

上面的輸出表示刪除了第一個父版本的"Hello world" 和第二個父版本的"Goodbye", 然後加入了 "Goodbye world".

有些diff選項可以對比工作目錄和三個暫存中的任何一個的差異:

1
2
3
4
5
6
git diff -1 file.txt
git diff --base file.txt
git diff -2 file.txt
git diff --ours file.txt
git diff -3 file.txt
git diff --theirs file.txt

git loggitk 命令也爲合併提供了特別的協助:

1
2
git log --merge
gitk --merge

這會顯示那些只在HEAD或只在MERGE_HEAD中的提交, 還有那些更新了爲合併文件的提交. 也可以使用 git mergetool, 它允許你使用外部工具如emacskdiff3 去合併文件, 在每次解決衝突之後都應該使用 git add 更新索引.

完成索引更新之後, git diff 默認不再顯示那個文件的差異, 所以那個文件的不同暫存版本被摺疊了起來.

多路合併

可以一次合併多個頭, 只需要簡單把它們作爲git merge的參數列出:

1
git merge scott/master rick/master tom/master

等價於:

1
2
3
git merge scott/master
git merge rick/master
git merge tom/master

子樹

有時會出現你想要在項目中引入其他獨立開發項目的內容的情況. 在沒有路徑衝突的前提下, 只需簡單的從其他項目拉取內容即可, 如果有衝突文件, 你可以選擇合併這些衝突, 但是更好的解決方案是把外部項目作爲一個子目錄進行合併, 這種情況不被遞歸合併策略支持, 而需要子樹合併策略.

假設你有一個倉庫位於 /path/to/B, 你想要合併這個倉庫的master分支到你當前倉庫的dir-B子目錄下:

1
2
3
4
5
git remote add -f Bproject /path/to/B
git merge -s ours --no-commit Bproject/master
git read-tree --prefix=dir-B/ -u Bproject/master
git commit -m "Merge B project as our subdirectory"
git pull -s subtree Bproject master

子樹合併的好處是它並沒有給你倉庫的用戶增加太多的管理負擔, 它兼容於較老版本的客戶端, 克隆完成之後馬上可以得到代碼. 然後如果過你使用子模塊, 你可以選擇不傳輸這些子模塊對象, 這可能在子樹合併過程中造成問題. 另外, 如果你需要修改內嵌外部項目的內容, 使用子模塊方式可以更容易地提交修改.

查找問題的利器

Git Bisect

假設你在項目的 2.6.18 版本上工作, 但是你當前的代碼崩潰了. 有時解決這種問題的最好辦法是: 手工逐步恢復項目歷史, 找出是哪個提交導致了問題. 但是git-bisect 可以更好的幫你解決問題:

1
2
3
git bisect start
git bisect good v2.6.18
git bisect bad master

如果你現在運行 git branch, 會發現你現在所在的是 "no branch", 這時分支指向提交 "69543", 此提交剛好是 "v2.6.18" 和 "master" 中間的位置. 現在在這個分支裏編譯並測試項目代碼, 如果它崩潰了, 那麼運行下面的命令:

1
git bisect bad

現在git會自動檢出一個更老的版本. 繼續這樣做, git會使用二分查找檢出good和bad之間的一個版本, 當找出導致問題的提交, 運行:

1
git bisect reset

回到執行 git bisect start 之間的狀態. 也可以手工選擇版本:

1
git bisect visualize

這會運行gitk, 界面上會標識出 git bisect 命令自動選擇的提交. 你可以選擇一個相鄰的提交, 記住它的SHA值. 用下面的命令檢出:

1
git reset --hard fb47ddb

這樣反覆執行, 直到找出導致問題的提交爲止.

Git Blame

如果你要查看文件的每個部分是誰修改的, 那麼 git blame 是不二選擇. 只要運行該命令, 你就會得到整個文件的每一行的詳細修改信息, 包括SHA, 日期和作者.

1
git blame sha1_file.c

如果文件被修改了或是編譯失敗了, 可以通過這個命令去查找導致錯誤的詳細信息. 可以用 -L 參數指定開始和結束行:

1
git blame -L160,+10 sha1_file.c

Git和Email

向一個項目提交補丁

如果只做了少量改動, 最簡單的提交方式就是把它們做成補丁用郵件發送出去. 首先生成補丁:

1
git format-patch origin

這會在當前目錄生成一個系統編號的補丁文件, 包含了當前分支和origin/HEAD之間的差異內容. 然後通過 git send-email 命令利用郵件發送補丁.

向一個項目中導入補丁

Git提供了一個名爲 git am 的工具去應用哪些通過email寄來的系列補丁. 你需要將所有包含補丁的消息存入單個的mailbox文件, 例如 patches.mbox, 然後運行:

1
git am -3 patches.mbox

Git會按照順序應用每一個補丁, 如果發生衝突, Git會讓你手工解決衝突而完成合並. -3選項讓git執行合併操作, 如果你需要中止並不改動你的工作樹和索引可以省略該選項. 在解決衝突和更新所有之後, 不需要在創建一個新的提交, 只需要運行:

1
git am --resolved

這時git會爲你創建一個提交, 然後繼續應用mailbox中餘下的補丁.

最後的效果是, git產生了一系列提交, 每個提交是原來mailbox中的一個補丁, 補丁中的作者信息和提交日誌也被記錄下來.

定製Git

更改編輯器

1
git config --global core.editor vim

添加別名

1
2
3
git config --global alias.last "cat-file commit HEAD"
git last
git cat-file commit HEAD

添加顏色

1
2
3
4
git config color.branch auto
git config color.diff auto
git config color.interactive auto
git config color.status auto

或者通過以下命令把顏色全部打開:

1
git config color.ui true

提交模版

1
git config commit.template '/etc/git-commit-template'

日誌格式

1
git config format.pretty online

Git Hooks

鉤子(hooks)是一些在 "$GIT-DIR/hooks" 目錄的腳本, 在被特定的事件觸發後被調用, 默認情況下這些鉤子是不生效的, 把這些鉤子文件的 ".sample" 文件名後綴去掉就可以讓它們生效了.

applypatch-msg

該鉤子文件爲:

1
GIT_DIR/hooks/applypatch-msg

git-am 命令執行時這個鉤子被調用. 它只有一個參數: 就是存有提交信息的文件的名字. 如果鉤子的執行結果爲非零, 那麼補丁就不會被應用.

這個鉤子用於在其他地方編輯提交信息, 並且可以把這些消息規範成項目的標準. 也可以在分析完消息文件後拒絕此次提交.

pre-applypatch

該鉤子文件爲:

1
GIT_DIR/hooks/pre-applypatch

當'git-am'命令執行時, 這個鉤子就被調用. 它沒有參數, 並且是在一個補丁被應用後還未提交前被調用. 如果鉤子的執行結果是非零, 那麼剛纔應用的補丁就不會被提交.

它用於檢查當前的工作樹, 當提交的補丁不能通過特定的測試就拒絕將它提交進倉庫.

post-applypatch

該鉤子文件爲:

1
GIT_DIR/hooks/post-applypatch

當'git-am'命令執行時, 這個鉤子就被調用. 它沒有參數, 並且是在一個補丁被應用且在完成提交情況下被調用. 這個鉤子的主要用途是通知提示(notification), 它並不會影響'git-am'的執行和輸出.

pre-commit

該鉤子文件爲:

1
GIT_DIR/hooks/pre-commit

這個鉤子被 'git-commit' 命令調用,, 而且可以通過在命令中添加--no-verify 參數來跳過. 這個鉤子沒有參數, 在得到提交消息和開始提交前被調用. 如果鉤子執行結果是非零, 那麼 'git-commit' 命令就會中止執行. 當默認的'pre-commit'鉤子開啓時, 如果它發現文件尾部有空白行, 那麼就會中止此次提交.

prepare-commit-msg

該鉤子文件爲:

1
GIT_DIR/hooks/prepare-commit-msg

當'git-commit'命令執行時: 在編輯啓動前, 默認提交消息準備好後, 這個鉤子就被調用. 如果鉤子的執行結果是非零的話, 那麼'git-commit'命令就會被中止執行.

commit-msg

該鉤子文件爲:

1
GIT_DIR/hooks/commit-msg

當'git-commit'命令執行時, 這個鉤子被調用, 也可以在命令中添加--no-verify參數來跳過. 這個鉤子有一個參數: 就是被選定的提交消息文件的名字. 如這個鉤子的執行結果是非零, 那麼'git-commit'命令就會中止執行.

這個鉤子爲提交消息更適當, 可以用於規範提交消息使之符合項目的標準; 如果它檢查完提交消息後, 發現內容不符合某些標準, 它也可以拒絕此次提交. 默認的'commit-msg'鉤子啓用後, 它檢查裏面是否有重複的簽名結束線, 如果找到它就是中止此次提交操作.

post-commit

該鉤子文件爲:

1
GIT_DIR/hooks/post-commit

當'git-commit'命令執行時, 這個鉤子就被調用. 它沒有參數, 並且是在一個提交完成時被調用. 這個鉤子的主要用途是通知提示, 它並不會影響'git-commit'的執行和輸出.

pre-rebase

該鉤子文件爲:

1
GIT_DIR/hooks/pre-rebase

當'git-base'命令執行時, 這個鉤子就被調用, 主要目的是阻止那些不應被rebase的分支被rebase(例如, 一個已經發布的分支提交就不應被rebase).

post-checkout

該鉤子文件爲:

1
GIT_DIR/hooks/post-checkout

當'git-checkout'命令更新完整個工作樹後, 這個鉤子就會被調用. 這個鉤子有三個參數: 前一個HEAD的 ref, 新HEAD的 ref, 判斷一個簽出是分支簽出還是文件簽出的標識符(分支簽出=1, 文件簽出=0). 這個鉤子不會影響'git-checkout'命令的輸出. 這個鉤子可以用於檢查倉庫的一致性, 自動顯示簽出前後的代碼的區別, 也可以用於設置目錄的元數據屬性.

post-merge

該鉤子文件爲:

1
GIT_DIR/hooks/post-merge

pre-receive

當用戶在本地倉庫執行 git push 命令時, 服務端上遠程倉庫就會對應執行 git-receive-pack 命令, 該命令會調用pre-receive 鉤子. 在開始更新遠程倉庫上的ref之前, 這個鉤子被調用. 鉤子的執行結果決定此次更新能否成功. 每次執行一個接受操作都會調用一次這個鉤子. 它沒有命令行參數, 但是它會從標準輸入讀取需要更新的ref, 格式如下:

1
<old-value> SP <new-value> SP <ref-name> LF

其中SP是空格, LF是回車. 是保存在ref中的老對象的名字, 是保存在ref裏的新對象的名字, 是此次更新的ref的全名. 如果是創建一個新的ref, 那麼 就是由40個0組成的字符串. 如果鉤子的執行結果爲非零, 那麼沒有引用會被更新. 如果執行結果爲零, 更新操作還可以被後面的 <<update,'update>> 鉤子所阻止. 鉤子的標準輸出和標準錯誤輸出會通過 'git-send-pack' 轉發給客戶端.

update

該鉤子文件爲:

1
GIT_DIR/hooks/update

當用戶在本地倉庫執行'git-push'命令時, 服務器上遠端倉庫就會對應執行'git-receive-pack', 而'git-receive-pack'會調用 update 鉤子. 在更新遠程倉庫上的ref之前, 這個鉤子被調用. 鉤子的執行結果決定此次update能否成功.

post-receive

該鉤子文件爲:

1
GIT_DIR/hooks/post-receive

當用戶在本地倉庫執行'git-push'命令時, 服務器上遠端倉庫就會對應執行'git-receive-pack'命令, 在所有遠程倉庫的引用(ref)都更新後, 這個鉤子就會被'git-receive-pack'調用.

找回丟失的對象

準備

首先創建一個實驗用的倉庫:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$ mkdir recovery
$ cd recovery
$ git init
$ touch file
$ git add file
$ git commit -m "First commit"
$ echo "Hello World" > file
$ git add .
$ git commit -m "Greetings"
$ git branch cool_branch 
$ git checkout cool_branch
$ echo "What up world?" > cool_file
$ git add .
$ git commit -m "Now that was cool"
$ git checkout master
$ echo "What does that mean?" >> file

恢復已刪除的分支提交

現在repo中有兩個分支 (master和cool_branch), 首先存儲當前倉庫未提交的改動:

1
git stash save "temp save"

刪除一個分支:

1
git branch -D cool_branch

找出剛纔刪除的分支裏的提交對象:

1
git fsck --lost-found

查看對象的內容:

1
git show 2e43cd56ee4fb08664cd843cd32836b54fbf594a

恢復:

1
git rebase 2e43cd56ee4fb08664cd843cd32836b54fbf594a

也可以使用 git merge 進行恢復, 把剛纔恢復的提交刪除:

1
git reset --hard HEAD^

把剛刪除的提交找回:

1
git fsck --lost-found

用合併命令恢復:

1
git merge 2e43cd56ee4fb08664cd843cd32836b54fbf594a

git stash的恢復

在上一個演示中我們把沒有提交的內容利用 git stash 進行了存儲, 如果這個存儲不小心刪除了怎麼辦呢?

1
2
git stash list
git stash clear

先找回刪除:

1
git fsck --lost-found

然後恢復:

1
2
git show 674c0618ca7d0c251902f0953987ff71860cb067
git merge 674c0618ca7d0c251902f0953987ff71860cb067

子模塊

子模塊

一個大項目通常由很多較小的, 自完備的模塊組成.

1
2
3
4
5
6
7
8
9
10
11
12
mkdir ~/git
cd ~/git
for i in a b c d
do
mkdir $i
cd $i
git init
echo "module $i" > $i.txt
git add $i.txt
git commit -m "Initial commit, submodule $i"
cd ..
done

創建父項目, 加入所有的子模塊:

1
2
3
4
5
6
7
mkdir super
cd super
git init
for i in a b c d
do
git submodule add ~/git/$i $i
done

git submodule add 命令進行了如下的操作:

它在當前目錄下克隆各個子模塊, 默認檢出master分支. 把子模塊的克隆路徑加入到 gitmodules 文件中, 然後把這個文件加入到索引. 把子模塊的當前提交ID加入到索引中, 準備進行提交.

提交父項目:

1
git commit -m "Add submodules a, b, c and d."

現在克隆父項目:

1
2
3
$ cd ..
$ git clone super cloned
$ cd cloned

子模塊的目錄已經創建, 但是沒有內容, 然後拉取子模塊:

1
2
git submodule init
git submodule update

通過init將子模塊的url加入到 .git/config, 然後用udpate去克隆子模塊倉庫和檢出父項目中指定的版本, 子模塊處於頭指針分離狀態, 不在任何一個分支.

如果需要對子模塊進行修改, 那麼應該創建或檢出一個分支, 進行修改, 然後發佈子模塊的修改, 更新父項目讓其引用新的提交:

1
2
cd a
git checkout master

或者

1
2
cd a
git checkout -b fix-up

然後:

1
2
3
4
5
6
7
8
$ echo "adding a line again" >> a.txt
$ git commit -a -m "Updated the submodule from within the superproject."
$ git push
$ cd ..
$ git diff
$ git add a
$ git commit -m "Updated submodule a."
$ git push

如果需要更新子模塊, 應該在 git pull 之後運行 git submodule update.

子模塊方式的陷阱

應該總是在發佈父項目的修改之前發佈子模塊修改. 如果忘記發佈子模塊修改, 其他人就不能克隆你的倉庫:

1
2
3
4
5
6
7
8
9
10
$ cd ~/git/super/a
$ echo i added another line to this file >> a.txt
$ git commit -a -m "doing it wrong this time"
$ cd ..
$ git add a
$ git commit -m "Updated submodule a again."
$ git push
$ cd ~/git/cloned
$ git pull
$ git submodule update

如果你暫存了一個更新過的子模塊, 準備進行手工提交, 注意不要在路徑後面加上斜槓, git會認爲你想要移除哪個子模塊然後檢出那個目錄內容到父倉庫:

1
2
3
4
5
6
$ cd ~/git/super/a
$ echo i added another line to this file >> a.txt
$ git commit -a -m "doing it wrong this time"
$ cd ..
$ git add a/
$ git status

爲了修正這個錯誤, 我們應該重置這個修改:

1
2
3
$ git reset HEAD A
$ git add a
$ git status
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章