1 起步
Git 配置
/etc/gitconfig
文件:系統中對所有用戶都普遍適用的配置。若使用git config
時用--system
選項,讀寫的就是這個文件。~/.gitconfig
文件:用戶目錄下的配置文件只適用於該用戶。若使用git config
時用--global
選項,讀寫的就是這個文件。- 當前項目的 git 目錄中的配置文件(也就是工作目錄中的
.git/config
文件):這里的配置僅僅針對當前項目有效。每一個級別的配置都會覆蓋上層的相同配置,所以.git/config
里的配置會覆蓋/etc/gitconfig
中的同名變量。
2 Git 基礎
2.2 記錄每次更新到倉庫
忽略某些文件
文件 .gitignore
的格式規范如下:
- 所有空行或者以注釋符號
#
開頭的行都會被 Git 忽略。 - 可以使用標准的 glob 模式匹配。
- 匹配模式最后跟反斜杠(
/
)說明要忽略的是目錄。 - 要忽略指定模式以外的文件或目錄,可以在模式前加上驚嘆號(
!
)取反。
所謂的 glob 模式是指 shell 所使用的簡化了的正則表達式。星號(*
)匹配零個或多個任意字符;[abc]
匹配任何一個列在方括號中的字符(這個例子要么匹配一個 a,要么匹配一個 b,要么匹配一個 c);問號(?
)只匹配一個任意字符;如果在方括號中使用短划線分隔兩個字符,表示所有在這兩個字符范圍內的都可以匹配(比如 [0-9]
表示匹配所有 0 到 9 的數字)。
2.3 查看提交歷史
選項 | 說明 |
---|---|
-p |
按補丁格式顯示每個更新之間的差異。 |
--stat |
顯示每次更新的文件修改統計信息。 |
--shortstat |
只顯示 --stat 中最后的行數修改添加移除統計。 |
--name-only |
僅在提交信息后顯示已修改的文件清單。 |
--name-status |
顯示新增、修改、刪除的文件清單。 |
--abbrev-commit |
僅顯示 SHA-1 的前幾個字符,而非所有的 40 個字符。 |
--relative-date |
使用較短的相對時間顯示(比如,“2 weeks ago”)。 |
--graph |
顯示 ASCII 圖形表示的分支合並歷史。 |
--pretty |
使用其他格式顯示歷史提交信息。可用的選項包括 oneline,short,full,fuller 和 format(后跟指定格式)。 |
例如:
git log
git log -p -2
git log --stat
git log --pretty=oneline
git log --pretty=format:"%h - %an, %ar : %s"
git log --pretty=format:"%h %s" --graph
2.4 撤消操作
修改最后一次提交
git commit --amend
2.7 技巧和竅門
自動補全
如果你用的是 Bash shell,可以試試看 Git 提供的自動補全腳本。下載 Git 的源代碼,進入 contrib/completion
目錄,會看到一個 git-completion.bash
文件。將此文件復制到你自己的用戶主目錄中(譯注:按照下面的示例,還應改名加上點:cp git-completion.bash ~/.git-completion.bash
),並把下面一行內容添加到你的 .bashrc
文件中:
$ source ~/.git-completion.bash
Git 命令別名
$ git config --global alias.last 'log -1 HEAD'
3 Git 分支
Git 中是 branch 指向 commit 的指針,HEAD 是指向 branch 的指針!
3.5 遠程分支
我們用 (遠程倉庫名)/(分支名)
這樣的形式表示遠程分支。比如我們想看看上次同 origin
倉庫通訊時 master
分支的樣子,就應該查看origin/master
分支。
3.6 分支的衍合
一般我們使用衍合的目的,是想要得到一個能在遠程分支上干凈應用的補丁 — 比如某些項目你不是維護者,但想幫點忙的話,最好用衍合:先在自己的一個分支里進行開發,當准備向主項目提交補丁的時候,根據最新的 origin/master
進行一次衍合操作然后再提交,這樣維護者就不需要做任何整合工作
4 服務器上的 Git
遠程倉庫通常只是一個裸倉庫(bare repository) — 即一個沒有當前工作目錄的倉庫。因為該倉庫只是一個合作媒介,所以不需要從硬盤上取出最新版本的快照;倉庫里存放的僅僅是 Git 的數據。簡單地說,裸倉庫就是你工作目錄中 .git
子目錄內的內容。
5 分布式 Git
5.2 為項目作貢獻
建議:
- 首先,請不要在更新中提交多余的白字符(whitespace)。Git 有種檢查此類問題的方法,在提交之前,先運行
git diff --check
,會把可能的多余白字符修正列出來。 - 接下來,請將每次提交限定於完成一次邏輯功能。並且可能的話,適當地分解為多次小更新,以便每次小型提交都更易於理解。(如果針對兩個問題改動的是同一個文件,可以試試看
git add --patch
的方式將部分內容置入暫存區域)
涉及到的 Git 命令:
- 查看服務器新增提交:先
git fetch origin
,然后git log origin/master ^master
- 不產生提交的合並:
git merge --no-commit --squash featureB
5.3 項目的管理
檢出遠程分支
長期的合作,建立遠程分支,例如:
$ git remote add jessica git://github.com/jessica/myproject.git
$ git fetch jessica
$ git checkout -b rubyclient jessica/ruby-client
臨時合作,只需用 git pull
命令抓取遠程倉庫上的數據,例如:
$ git pull git://github.com/onetimeguy/project.git
From git://github.com/onetimeguy/project
* branch HEAD -> FETCH_HEAD
Merge made by recursive.
決斷代碼取舍
查看分支上都有哪些新增的提交
比如在 contrib
特性分支上打了兩個補丁,僅查看這兩個補丁的提交信息,可以用 --not
選項指定要屏蔽的分支 master
,這樣就會剔除重復的提交歷史:
$ git log contrib --not master
查看分支和其他分支間的差異
$ git diff master...contrib
這條命令相當於先用git merge-base contrib master
找到公共組件的 SHA-1,然后用git diff 公共組件的 SHA-1
代碼集成
一旦特性分支准備停當,接下來的問題就是如何集成到更靠近主線的分支中。此外還要考慮維護項目的總體步驟是什么。
合並流程
一般最簡單的情形,是在 master
分支中維護穩定代碼,然后在特性分支上開發新功能,或是審核測試別人貢獻的代碼,接着將它並入主干,最后刪除這個特性分支,如此反復。
衍合與挑揀(cherry-pick)的流程
挑揀類似於針對某次特定提交的衍合。它首先提取某次提交的補丁,然后試着應用在當前分支上。如果某個特性分支上有多個提交,但你只想引入其中之一就可以使用這種方法。也可能僅僅是因為你喜歡用挑揀,討厭衍合。例如git cherry-pick 挑揀的 SHA-1
給發行版簽名
加 tag
生成內部版本號
$ git describe master
准備發布
現在可以發布一個新的版本了。首先要將代碼的壓縮包歸檔,方便那些可憐的還沒有使用 Git 的人們。可以使用git archive
:
$ git archive master --prefix='project/' | gzip > `git describe master`.tar.gz
這個壓縮包解壓出來的是一個文件夾,里面是你項目的最新代碼快照。你也可以用類似的方法建立一個 zip 壓縮包,在git archive
加上--format=zip
選項:
$ git archive master --prefix='project/' --format=zip > `git describe master`.zip
現在你有了一個 tar.gz 壓縮包和一個 zip 壓縮包,可以把他們上傳到你網站上或者用 e-mail 發給別人。
制作簡報
使用git shortlog
命令可以方便快捷的制作一份修改日志(changelog),告訴大家上次發布之后又增加了哪些特性和修復了哪些 bug。實際上這個命令能夠統計給定范圍內的所有提交; 假如你上一次發布的版本是 v1.0.1,下面的命令將給出自從上次發布之后的所有提交的簡介:
$ git shortlog --no-merges master --not v1.0.1
6 Git 工具
6.1 修訂版本(Revision)選擇
祖先引用
^N
選擇上次父提交的第幾父提交(僅在合並提交時有用),~N
選擇向前第幾次的父提交
例如: git show HEAD~3^2
表示查看前三次的提交的第二父提交
zjf@DESKTOP-5JD9B9T MINGW64 ~/Desktop/test/git (master)
$ git log --pretty=format:'%h %s' --graph
* 1945ca3 c9 (git show HEAD)
* deb9846 c8 (git show HEAD~ 或 git show HEAD^)
* 62d2c92 c7 (git show HEAD~2 或 git show HEAD^^)
* cd081b7 c6 (git show HEAD~3)
|\
| * ee8af81 c4 (git show HEAD~3^2)
* | dc15520 c3 (git show HEAD~3^1)
* | f34e664 c2
|/
* 8919ec2 c1
zjf@DESKTOP-5JD9B9T MINGW64 ~/Desktop/test/git (master)
$ git show -q HEAD~3^2
commit ee8af8148af521f509c08ed8899643a0b4c88e6c (t1)
Author: zjf <zjffun@gmail.com>
Date: Sat Jul 13 19:09:34 2019 +0800
c4
提交范圍
- 雙點:
git log origin/master..HEAD
顯示在 HEAD 分支上,但不在 origin/master 分支上的提交 - 多點:
git log refA refB --not refC
顯示在 refA 或 refB 分支上,但不在 refC 分支上的提交 - 三點:
git log --left-right master...experiment
,查看 master 和 experiment 分支的不同提交,並標出不同的提交屬於哪個分支
6.2 交互式暫存
$ git add -i
在xxx>>
提示后面直接敲入回車會保存更改。
只讓 Git 暫存文件的某些部分而忽略其他也是有可能的。在交互式的提示符下,輸入5
或者p
(表示 patch,補丁)。Git 會詢問哪些文件你希望部分暫存;然后對於被選中文件的每一節,他會逐個顯示文件的差異區塊並詢問你是否希望暫存他們。
6.3 儲藏(Stashing)
stash 跟 commit 很像不過是存在一個單獨的棧里。
6.4 重寫歷史
改變最近一次提交
$ git commit --amend
修改、重排和壓制提交
# 修改最近三次提交(將要修改的 pick 改為 edit、squash,或者改變順序)
$ git rebase -i HEAD~3
# 根據提示輸入下面的命令修改或繼續
$ git commit --amend
$ git rebase --continue
拆分提交
在git rebase -i
腳本中修改你想拆分的提交前的指令為 "edit",然后在進入命令行后進行多次提交,然后git rebase --continue
核彈級選項: filter-branch
從所有提交中刪除一個文件
$ git filter-branch --tree-filter 'rm -f passwords.txt' HEAD
將一個子目錄設置為新的根目錄
假設你完成了從另外一個代碼控制系統的導入工作,得到了一些沒有意義的子目錄(trunk,
tags 等等)。如果你想讓trunk
子目錄成為每一次提交的新的項目根目錄,filter-branch
也可以幫你做到,使用下面這條命令 Git 會自動地刪除不對這個子目錄產生影響的提交。
$ git filter-branch --subdirectory-filter trunk HEAD
全局性地更換電子郵件地址
$ git filter-branch --commit-filter '
if [ "$GIT_AUTHOR_EMAIL" = "schacon@localhost" ];
then
GIT_AUTHOR_NAME="Scott Chacon";
GIT_AUTHOR_EMAIL="schacon@example.com";
git commit-tree "$@";
else
git commit-tree "$@";
fi' HEAD
6.5 使用 Git 調試
查看每行代碼的最近一次提交
例如:查看index.js
的 12-22 的每一行最近一次提交分別是由誰在哪一天弄的
git blame -L 12,22 index.js
PS:-C
參數會嘗試找出隱式的重命名動作。通常,你會把你拷貝代碼的那次提交作為原始提交,因為這是你在這個文件中第一次接觸到那幾行。Git 可以告訴你編寫那些行的原始提交,即便是在另一個文件里。
二分查找定位發生錯誤的提交
首先你運行git bisect start
啟動,然后你用git bisect bad
來告訴系統當前的提交已經有問題了。然后你必須告訴 bisect 已知的最后一次正常狀態是哪次提交,使用git bisect good [good_commit]
:
$ git bisect start
$ git bisect bad
$ git bisect good v1.0
然后告訴 git 當前版本是否有錯誤
$ git bisect good
$ git bisect bad
當你完成之后,你應該運行git bisect reset
來重設你的 HEAD 到你開始前的地方,否則你會處於一個詭異的地方:
$ git bisect reset
6.6 子模塊
子模塊允許你將一個 Git 倉庫當作另外一個 Git 倉庫的子目錄。這允許你克隆另外一個倉庫到你的項目中並且保持你的提交相對獨立。
$ git submodule
6.7 子樹合並
子樹歸並的思想是你擁有兩個工程,其中一個項目映射到另外一個項目的子目錄中,反過來也一樣。當你指定一個子樹歸並,Git 可以聰明地探知其中一個是另外一個的子樹從而實現正確的歸並——這相當神奇。
# 拉取分支
$ git read-tree
# 對比分支
$ git diff-tree
7 自定義 Git
先過一遍第一章中提到的 Git 配置細節。Git 使用一系列的配置文件來存儲你定義的偏好,它首先會查找
/etc/gitconfig
文件,該文件含有 對系統上所有用戶及他們所擁有的倉庫都生效的配置值(譯注:gitconfig 是全局配置文件), 如果傳遞--system
選項給git config
命令, Git 會讀寫這個文件。接下來 Git 會查找每個用戶的
~/.gitconfig
文件,你能傳遞--global
選項讓 Git 讀寫該文件。最后 Git 會查找由用戶定義的各個庫中 Git 目錄下的配置文件(
.git/config
),該文件中的值只對屬主庫有效。 以上闡述的三層配置從一般到特殊層層推進,如果定義的值有沖突,以后面層中定義的為准,例如:在.git/config
和/etc/gitconfig
的較量中,.git/config
取得了勝利。雖然你也可以直接手動編輯這些配置文件,但是運行git config
命令將會來得簡單些。
7.1 配置 Git
客戶端基本配置
# 用戶名和郵箱
$ git config --global user.name "John Doe"
$ git config --global user.email johndoe@example.com
# 文本編輯器
$ git config --global core.editor vim
# 提交模板文件
$ git config --global commit.template $HOME/.gitmessage.txt
# 分頁器(默認用的是 less)
$ git config --global core.pager ''
# GPG簽署密鑰
$ git config --global user.signingkey <gpg-key-id>
# 忽略的文件
$ git config --global core.excludesfile
# 只有一個命令被模糊匹配到的情況下,Git 會自動運行該命令
$ git config --global help.autocorrect
# 輸出着色
$ git config --global color.ui true
# 外部的合並與比較工具(P4Merge)
git config --global merge.tool
git config --global mergetool
git config --global diff.tool
git config --global difftool
# 處理行尾結束符(回車換行)
core.autocrlf
# 修正空白符問題
core.whitespace
服務器基本配置
# 強制推送時 Git 都檢查一致性
$ git config --system receive.fsckObjects true
# 禁用“改變歷史”的推送
$ git config --system receive.denyNonFastForwards true
# 禁用刪除分支
$ git config --system receive.denyDeletes true
7.2 Git 屬性
一些設置項也能被運用於特定的路徑中,這樣,Git 以對一個特定的子目錄或子文件集運用那些設置項。這些設置項被稱為 Git 屬性,可以在你目錄中的
.gitattributes
文件內進行設置(通常是你項目的根目錄),也可以當你不想讓這些屬性文件和項目文件一同提交時,在.git/info/attributes
進行設置。
二進制文件
# 讓 Git 把所有pbxproj文件(看着像文本文件,但通常比較其內容)當成二進制文件
# 現在 Git 會嘗試轉換和修正CRLF(回車換行)問題,但不會進行比較
*.pbxproj -crlf -diff
# Git 1.6及之后的版本中,可以用一個宏代替-crlf -diff
*.pbxproj binary
# 把Word文檔轉換成可讀的文本文件,之后再進行比較
$ echo '*.doc diff=word' >> .gitattributes
$ git config diff.word.textconv strings
# 對比圖像的元數據
$ echo '*.png diff=exif' >> .gitattributes
$ git config diff.exif.textconv exiftool
關鍵字擴展
# 把blob的SHA-1校驗和自動注入文件的$Id$字段
$ echo '*.txt ident' >> .gitattributes
$ echo '$Id$' > test.txt
$ rm text.txt
$ git checkout -- text.txt
$ cat test.txt
$Id: 42812b7653c7b88933f8a9d6cad0ca16714b9bb3 $
過濾器:
暫存區 -> 工作區:smudge
工作區 -> 暫存區:clean
# 暫存文件時調整縮進
$ echo '*.c filter=indent' >> .gitattributes
$ git config --global filter.indent.clean indent
$ git config --global filter.indent.smudge cat
導出倉庫
# 不歸檔測試文件夾
$ echo 'test/ export-ignore' >> .gitattributes
合並策略
# 對 database.xml 只用 ours 這邊的進行合並
database.xml merge=ours
7.3 Git 掛鈎
有兩組掛鈎:客戶端和服務器端。客戶端掛鈎用於客戶端的操作,如提交和合並。服務器端掛鈎用於 Git
服務器端的操作,如接收被推送的提交。
客戶端掛鈎
提交工作流掛鈎:pre-commit
、prepare-commit-msg
、commit-msg
、post-commit
電子郵件工作流掛鈎:如果你通過 e-mail 接收由git format-patch
產生的補丁,這些掛鈎也許對你有用。
其他客戶端掛鈎:pre-rebase
、git checkout
服務器端掛鈎
pre-receive
、post-receive
、update
7.4 Git 強制策略實例(○)
本節中我們應用前面學到的知識建立這樣一個 Git 工作流程:檢查提交信息的格式,只接受純 fast-forward 內容的推送,並且指定用戶只能修改項目中的特定子目錄。我們將寫一個客戶端腳本來提示開發人員他們推送的內容是否會被拒絕,以及一個服務端腳本來實際執行這些策略。
8 Git 與其他系統(○)
9 Git 內部原理
從根本上來講 Git 是一套內容尋址 (content-addressable) 文件系統,在此之上提供了一個 VCS 用戶界面。
由於 Git 一開始被設計成供 VCS 使用的工具集而不是一整套用戶友好的 VCS,它還包含了許多底層命令,這些命令用於以 UNIX 風格使用或由腳本調用。這些命令一般被稱為 "plumbing" 命令(底層命令),其他的更友好的命令則被稱為 "porcelain" 命令(高層命令)。
當你在一個新目錄或已有目錄內執行 git init
時,Git 會創建一個 .git
目錄,幾乎所有 Git 存儲和操作的內容都位於該目錄下。如果你要備份或復制一個庫,基本上將這一目錄拷貝至其他地方就可以了。
該目錄結構如下:
$ ls
HEAD ***指向當前分支
branches/ 新版本的 Git 不再使用
config 項目特有的配置選項
description 僅供 GitWeb 程序使用
hooks/ 目錄保存了第七章詳細介紹了的客戶端或服務端鈎子腳本
index ***保存了暫存區域信息
info/ 保存了一份不希望在 .gitignore 文件中管理的忽略模式 (ignored patterns)
objects/ ***存儲所有數據內容
refs/ ***存儲指向數據 (分支) 的提交對象的指針
9.2 objects
Git 從核心上來看不過是簡單地存儲鍵值對(key-value)。它允許插入任意類型的內容,並會返回一個鍵值,通過該鍵值可以在任何時候再取出該內容。可以通過底層命令 hash-object
來示范這點,傳一些數據給該命令,它會將數據保存在 .git
目錄並返回表示這些數據的鍵值。
Blob
# 初始化Git倉庫
$ mkdir test
$ cd test
$ git init
Initialized empty Git repository in /tmp/test/.git/
# objects 目錄是空的
$ find .git/objects -type f
# 創建 Git 對象
$ echo 'test content' | git hash-object -w --stdin
d670460b4b4aece5915caf5c68d12f560a9fe3e4
# 查看在 objects 目錄生成的文件(Git 為每份內容生成一個文件)
$ find .git/objects -type f
.git/objects/d6/70460b4b4aece5915caf5c68d12f560a9fe3e4
# 讀取 Git 對象內容
$ git cat-file -p d670460b4b4aece5915caf5c68d12f560a9fe3e4
test content
# 創建 Git 對象(version 1)
$ echo 'version 1' > test.txt
$ git hash-object -w test.txt
83baae61804e65cc73a7201a7252750c76066a30
# 創建 Git 對象(version 2)
$ echo 'version 2' > test.txt
$ git hash-object -w test.txt
1f7a7a472abf3dd9643fd615f6da379c4acb3e3a
# 查看在 objects 目錄生成的文件
$ find .git/objects -type f
.git/objects/1f/7a7a472abf3dd9643fd615f6da379c4acb3e3a
.git/objects/83/baae61804e65cc73a7201a7252750c76066a30
.git/objects/d6/70460b4b4aece5915caf5c68d12f560a9fe3e4
# 恢復 version 1 文件
$ git cat-file -p 83baae61804e65cc73a7201a7252750c76066a30 > test.txt
$ cat test.txt
version 1
# 恢復 version 2 文件
$ git cat-file -p 1f7a7a472abf3dd9643fd615f6da379c4acb3e3a > test.txt
$ cat test.txt
version 2
tree
通常 Git 根據你的暫存區域或 index 來創建並寫入一個 tree 。
在上面已經創建了文件,需要指明將哪個文件索引(文件和版本)寫入樹。這需要將文件加入暫存區。
# 由於該文件原先並不在暫存區域中 (甚至就連暫存區域也還沒被創建出來呢) ,必須傳入 `--add` 參數
# 由於要添加的文件並不在當前目錄下而是在數據庫中,必須傳入`--cacheinfo` 參數
# 同時指定了文件模式(100644 ),SHA-1 值和文件名
$ git update-index --add --cacheinfo 100644 \
83baae61804e65cc73a7201a7252750c76066a30 test.txt
# 將索引寫入樹1
$ git write-tree
d8329fc1cc938780ffdd9f94e0d364e0ea74f579
# 查看這顆樹1
$ git cat-file -p d8329fc1cc938780ffdd9f94e0d364e0ea74f579
100644 blob 83baae61804e65cc73a7201a7252750c76066a30 test.txt
# 查看剛才創建的樹的類型
$ git cat-file -t d8329fc1cc938780ffdd9f94e0d364e0ea74f579
tree
# 更新 test.txt 索引
$ git update-index test.txt
# 創建新文件並建立索引
$ echo 'new file' > new.txt
$ git update-index --add new.txt
# 將索引寫入樹2
$ git write-tree
0155eb4229851634a0f03eb265b69f5a2d56f341
# 查看這顆樹2
$ git cat-file -p 0155eb4229851634a0f03eb265b69f5a2d56f341
100644 blob fa49b077972391ad58037050f2a75f74e3671e92 new.txt
100644 blob 1f7a7a472abf3dd9643fd615f6da379c4acb3e3a test.txt
# 將第一次創建的樹作為子目錄加入索引
$ git read-tree --prefix=bak d8329fc1cc938780ffdd9f94e0d364e0ea74f579
# 將索引寫入樹3
$ git write-tree
3c4e9cd789d88d8d89c1073707c3585e41b0e614
# 查看這棵樹3
$ git cat-file -p 3c4e9cd789d88d8d89c1073707c3585e41b0e614
040000 tree d8329fc1cc938780ffdd9f94e0d364e0ea74f579 bak
100644 blob fa49b077972391ad58037050f2a75f74e3671e92 new.txt
100644 blob 1f7a7a472abf3dd9643fd615f6da379c4acb3e3a test.txt
commit
# 使用樹1創建 commit 對象
$ echo 'first commit' | git commit-tree d8329f
fdf4fc3344e67ab068f836878b6c4951e3b15f3d
# 查看 commit 對象
$ git cat-file -p fdf4fc3
tree d8329fc1cc938780ffdd9f94e0d364e0ea74f579
author Scott Chacon <schacon@gmail.com> 1243040974 -0700
committer Scott Chacon <schacon@gmail.com> 1243040974 -0700
first commit
# 使用樹2、樹3創建 commit 對象
$ echo 'second commit' | git commit-tree 0155eb -p fdf4fc3
cac0cab538b970a37ea1e769cbbde608743bc96d
$ echo 'third commit' | git commit-tree 3c4e9c -p cac0cab
1a410efbd13591db07496601ebc7a059dd55cfe9
# 現在已經有了真實的 Git 歷史了
# 如果運行 git log 命令並指定最后那個 commit 對象的 SHA-1 便可以查看歷史
$ git log --stat 1a410e
commit 1a410efbd13591db07496601ebc7a059dd55cfe9
Author: Scott Chacon <schacon@gmail.com>
Date: Fri May 22 18:15:24 2009 -0700
third commit
bak/test.txt | 1 +
1 files changed, 1 insertions(+), 0 deletions(-)
commit cac0cab538b970a37ea1e769cbbde608743bc96d
Author: Scott Chacon <schacon@gmail.com>
Date: Fri May 22 18:14:29 2009 -0700
second commit
以下所列是目前為止樣例中的所有對象,每個對象后面的注釋里標明了它們保存的內容:
$ find .git/objects -type f
.git/objects/01/55eb4229851634a0f03eb265b69f5a2d56f341 # tree 2
.git/objects/1a/410efbd13591db07496601ebc7a059dd55cfe9 # commit 3
.git/objects/1f/7a7a472abf3dd9643fd615f6da379c4acb3e3a # test.txt v2
.git/objects/3c/4e9cd789d88d8d89c1073707c3585e41b0e614 # tree 3
.git/objects/83/baae61804e65cc73a7201a7252750c76066a30 # test.txt v1
.git/objects/ca/c0cab538b970a37ea1e769cbbde608743bc96d # commit 2
.git/objects/d6/70460b4b4aece5915caf5c68d12f560a9fe3e4 # 'test content'
.git/objects/d8/329fc1cc938780ffdd9f94e0d364e0ea74f579 # tree 1
.git/objects/fa/49b077972391ad58037050f2a75f74e3671e92 # new.txt
.git/objects/fd/f4fc3344e67ab068f836878b6c4951e3b15f3d # commit 1
9.3 refs
Git 中可以用一個簡單的名字來記錄 SHA-1 值。你可以在 .git/refs
目錄下面找到這些包含 SHA-1
值的文件。
# 查看 refs 文件夾
$ find .git/refs
.git/refs
.git/refs/heads
.git/refs/tags
# 查看 refs 文件夾的文件(現在還沒有是空的)
$ find .git/refs -type f
# 創建一個新的引用幫助你記住最后一次提交,技術上你可以這樣做
$ echo "1a410efbd13591db07496601ebc7a059dd55cfe9" > .git/refs/heads/master
# 現在,你就可以在 Git 命令中使用你剛才創建的引用而不是 SHA-1 值
$ git log --pretty=oneline master
1a410efbd13591db07496601ebc7a059dd55cfe9 third commit
cac0cab538b970a37ea1e769cbbde608743bc96d second commit
fdf4fc3344e67ab068f836878b6c4951e3b15f3d first commit
# 當然,我們並不鼓勵你直接修改這些引用文件,Git 提供了一個安全的命令 update-ref
$ git update-ref refs/heads/master 1a410efbd13591db07496601ebc7a059dd55cfe9
$ git update-ref refs/heads/test cac0ca
$ git log --pretty=oneline test
cac0cab538b970a37ea1e769cbbde608743bc96d second commit
fdf4fc3344e67ab068f836878b6c4951e3b15f3d first commit
HEAD 文件是一個指向你當前所在分支的引用標識符。
$ cat .git/HEAD
ref: refs/heads/master
$ git checkout test
$ cat .git/HEAD
ref: refs/heads/test
Tag 對象指向一個 commit 而不是一個 tree。它就像是一個分支引用,但是不會變化——永遠指向同一個 commit,僅僅是提供一個更加友好的名字。
如果你添加了一個 remote 然后推送代碼過去,Git 會把你最后一次推送到這個 remote 的每個分支的值都記錄在 refs/remotes
目錄下。遠程引用和分支主要區別在於他們是不能被 check out 的。Git 把他們當作是標記這些了這些分支在服務器上最后狀態的一種書簽。
# 添加一個叫做 origin 的 remote 然后把你的 master 分支推送上去
$ git remote add origin git@github.com:schacon/simplegit-progit.git
$ git push origin master
Counting objects: 11, done.
Compressing objects: 100% (5/5), done.
Writing objects: 100% (7/7), 716 bytes, done.
Total 7 (delta 2), reused 4 (delta 1)
To git@github.com:schacon/simplegit-progit.git
a11bef0..ca82a6d master -> master
# 查看 refs/remotes/origin/master 這個文件
$ cat .git/refs/remotes/origin/master
ca82a6dff817ec66f44342007202690a93763949
9.4 Packfiles(○)
9.5 The Refspec
查看.git/config
文件,有下面這樣的配置指定了遠程的名稱 (origin
), 遠程倉庫的 URL 地址,和用於獲取操作的 Refspec
[remote "origin"]
url = git@github.com:schacon/simplegit-progit.git
fetch = +refs/heads/*:refs/remotes/origin/*
Refspec 的格式是一個可選的 +
號,接着是 <src>:<dst>
的格式,這里 <src>
是遠端上的引用格式, <dst>
是將要記錄在本地的引用格式。可選的 +
號告訴 Git 在即使不能快速演進的情況下,也去強制更新它。
缺省情況下 refspec 會被 git remote add
命令所自動生成, Git 會獲取遠端上 refs/heads/
下面的所有引用,並將它寫入到本地的 refs/remotes/origin/
。
# 這三種寫法等效
$ git log origin/master
$ git log remotes/origin/master
$ git log refs/remotes/origin/master
# 拉取遠程的 master 分支到本地的 origin/mymaster 分支
$ git fetch origin master:refs/remotes/origin/mymaster
# 可以在命令行上指定多個 refspec
$ git fetch origin master:refs/remotes/origin/mymaster \
topic:refs/remotes/origin/topic
From git@github.com:schacon/simplegit
! [rejected] master -> origin/mymaster (non fast forward)
* [new branch] topic -> origin/topic
# 推送 refspec
$ git push origin master:refs/heads/qa/master
# 刪除遠程分支
$ git push origin :topic
一些設置:
# 只拉取遠程的 master 分支,可以把 fetch 這一行修改
fetch = +refs/heads/master:refs/remotes/origin/master
# 每次獲取 master 分支和 QA 組的所有分支
[remote "origin"]
url = git@github.com:schacon/simplegit-progit.git
fetch = +refs/heads/master:refs/remotes/origin/master
fetch = +refs/heads/qa/*:refs/remotes/origin/qa/*
# 指定默認推送
[remote "origin"]
url = git@github.com:schacon/simplegit-progit.git
fetch = +refs/heads/*:refs/remotes/origin/*
push = refs/heads/master:refs/heads/qa/master
9.6 傳輸協議
Git 可以以兩種主要的方式跨越兩個倉庫傳輸數據:基於 HTTP 協議之上,和
file://
,ssh://
, 和git://
等智能傳輸協議。
9.7 維護及數據恢復
數據恢復
通常最快捷的辦法是使用
git reflog
工具。當你 (在一個倉庫下) 工作時,Git 會在你每次修改了 HEAD 時悄悄地將改動記錄下來。當你提交或修改分支時,reflog 就會更新。
PS: reflog 數據是保存在 .git/logs/ 目錄下的。
$ git reflog
1a410ef HEAD@{0}: 1a410efbd13591db07496601ebc7a059dd55cfe9: updating HEAD
ab1afef HEAD@{1}: ab1afef80fac8e34258ff41fc1b867c702daa24b: updating HEAD
移除對象
Git 有許多過人之處,不過有一個功能有時卻會帶來問題:
git clone
會將包含每一個文件的所有歷史版本的整個項目下載下來。如果項目包含的僅僅是源代碼的話這並沒有什么壞處,畢竟 Git
可以非常高效地壓縮此類數據。不過如果有人在某個時刻往項目中添加了一個非常大的文件,那們即便他在后來的提交中將此文件刪掉了,所有的簽出都會下載這個大文件。因為歷史記錄中引用了這個文件,它會一直存在着。
# 底層命令 git verify-pack 可以識別出大對象
$ git verify-pack -v .git/objects/pack/pack-3f8c0...bb.idx | sort -k 3 -n | tail -3
e3f094f522629ae358806b17daf78246c27c007b blob 1486 734 4667
05408d195263d853f09dca71d55116663690c27c blob 12908 3478 1189
7a9eb2fba2b1811321254ac360970fc169ba2330 blob 2056716 2056872 5401
# 給 rev-list 命令傳入 --objects 選項,它會列出所有 commit SHA 值
$ git rev-list --objects --all | grep 7a9eb2fb
7a9eb2fba2b1811321254ac360970fc169ba2330 git.tbz2
# 找出哪些 commit 修改了這個文件
$ git log --pretty=oneline --branches -- git.tbz2
da3f30d019005479c99eb4c3406225613985a1db oops - removed large tarball
6df764092f3e7c8f5f94cbe08ee5cf42e92a0289 added git tarball
# 必須重寫從 6df76 開始的所有 commit 才能將文件從 Git 歷史中完全移除
$ git filter-branch --index-filter \
'git rm --cached --ignore-unmatch git.tbz2' -- 6df7640^..
Rewrite 6df764092f3e7c8f5f94cbe08ee5cf42e92a0289 (1/2)rm 'git.tbz2'
Rewrite da3f30d019005479c99eb4c3406225613985a1db (2/2)
Ref 'refs/heads/master' was rewritten
# 刪除 reflog 以及運行 filter-branch 時 Git 往 .git/refs/original 添加的一些 refs 對它的引用
$ rm -Rf .git/refs/original
$ rm -Rf .git/logs/
# repack
$ git gc
Counting objects: 19, done.
Delta compression using 2 threads.
Compressing objects: 100% (14/14), done.
Writing objects: 100% (19/19), done.
Total 19 (delta 3), reused 16 (delta 1)
# 查看結果
$ git count-objects -v
count: 8
size: 2040
in-pack: 19
packs: 1
size-pack: 7
prune-packable: 0
garbage: 0