Git 有三種狀態,你的文件可能處於其中之一:已提交(committed)、已修改(modified)和已暫存(staged)。 已提交表示數據已經全的保存在本地數據庫中。 已修改表示修改了文件,但還沒保存到數據庫中。 已暫存表示對一個已修改文件的當前版本作了標記,使之包含在下次提交的快照中。node
基本的 Git 工做流程以下:git
若是 Git 目錄中保存着的特定版本文件,就屬於已提交狀態。 若是做了修改並已放入暫存區域,就屬於已暫存狀態。 若是自上次取出後做了修改但尚未放到暫存區域,就是已修改狀態github
請記住,你工做目錄下的每個文件都不外乎這兩種狀態:已跟蹤未跟蹤。 已跟蹤的文件是指那些被歸入了版本控制的文件,在上一次快照中有它們的記錄,在工做一段時間後,它們的狀態可能處於未修改,已修改或已放入暫存區。 工做目錄中除已跟蹤文件之外的全部其它文件都屬於未跟蹤文件,它們既不存在於上次快照的記錄中,也沒有放入暫存區。 初次克隆某個倉庫的時候,工做目錄中的全部文件都屬於已跟蹤文件,並處於未修改狀態。正則表達式
編輯過某些文件以後,因爲自上次提交後你對它們作了修改,Git 將它們標記爲已修改文件。咱們逐步將這些修改過的文件放入暫存區而後提交全部暫存了的修改,如此反覆。因此使用 Git 時文件的生命週期以下:(圖片中Unmodified能夠理解爲上面的commited 已提交狀態)shell
咱們能夠建立一個名爲 .gitignore 的文件,列出要忽略的文件模式數據庫
文件 .gitignore 的格式規範以下:vim
所謂的 glob 模式是指 shell 所使用的簡化了的正則表達式。 星號()匹配零個或多個任意字符; [abc] 匹配任何一個列在方括號中字符(這個例子要麼匹配一個 a,要麼匹配一個b,要麼匹配一個c);問號( ? )只匹配一個任意字符;若是在方括號中使用短劃線分隔兩個字符,表示全部在這兩個字符範圍內的均可以匹配(好比 [0-9] 示匹配全部 0 到 9 的數字)。 使用兩個星號( ) 表示匹配任意中間目錄,好比 a/**/z 能夠匹配 a/z , a/b/z 或a/b/c/z 等。緩存
咱們再看一個 .gitignore 文件的例子:安全
# no .a files
*.a
# but do track lib.a, even though you're ignoring .a files above
!lib.a
# only ignore the TODO file in the current directory, not subdir/TODO
/TODO
# ignore all files in the build/ directory
build/
# ignore doc/notes.txt, but not doc/server/arch.txt
doc/*.txt
# ignore all .pdf files in the doc/ directory
doc/**/*.pdf
複製代碼
要查看還沒有暫存的文件更新了哪些部分,不加參數直接輸入 git diffruby
若要查看已暫存的將要添加到下次提交裏的內容,能夠用 git diff --cached 命令。(Git 1.6.1 及更高版本還容許使用 git diff --staged ,效果是相同的,但更好記些。)
請注意,git diff 自己只顯示還沒有暫存的改動,而不是自上次提交以來所作的全部改動。 因此有時候你一會兒暫存了全部更新過的文件後,運行 git diff 後卻什麼也沒有,就是這個緣由。
git commit -m 'xxxx'
複製代碼
請記住,提交時記錄的是放在暫存區域的快照。 任何還未暫存的然保持已修改狀態,能夠在下次提交時歸入版本管理。 每一次運行提交操做,都是對你項目做一次快照,之後能夠回到這個狀態,或者進行比較。
git commit 加上 -a 選項,Git 就會自動把全部已經跟蹤過的文件存起來一併提交,從而跳過 git add 步驟:
要從 Git 中移除某個文件,就必需要從已跟蹤文件清單中移除(確地說,是從暫存區域移除),而後提交。 能夠用 git rm 命令完成此工做,並連帶從工做目錄中刪除指定的文件,這樣之後就不會出如今未跟蹤文件清單中了。
有時候咱們提交完了才發現漏掉了幾個文件沒有添加行帶有 --amend 選項的提交命令嘗試從新提交:
git commit --amend
複製代碼
文本編輯器啓動後,能夠看到以前的提交信息。 編輯後保存會覆蓋原來的提交信息。 例如,你提交後發現忘記了暫存某些須要的修改,能夠像下面這樣操做:
$ git commit -m 'initial commit'
$ git add forgotten_file
$ git commit --amend
複製代碼
最終你只會有一個提交 - 第二次提交將代替第一次提交的結果。
接下來的兩個小節演示如何操做暫存區域與工做目錄中已修改的文件。 這些命令在修改文件狀態的同時,也會提示如何撤消操做。 例如,你已經修改了兩個文件而且想要將它們做爲兩次獨立的修改提交,可是卻意外地輸入了 git add * 暫存了它們兩個。 如何只取消暫存兩個中的一個呢? git status 命令提示了你:
$ git add *
$ git status
On branch master
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
renamed: README.md -> README
modified: CONTRIBUTING.md
複製代碼
在 Changes to be committed
文字正下方,提示使用 `git reset HEAD … 來取消暫存。 所 以,咱們能夠這樣來取消暫存 CONTRIBUTING.md 文件:
$ git reset HEAD CONTRIBUTING.md
Unstaged changes after reset:
M CONTRIBUTING.md
$ git status
On branch master
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
renamed: README.md -> README
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)
modified: CONTRIBUTING.md
複製代碼
若是你並不想保留對 CONTRIBUTING.md 文件的修改怎麼辦? 你該如何方便地撤消修改 - 將它還原成上次提交時的樣子(或者剛克隆完的樣子,或者剛把它放入工做目錄時的樣子)? 幸運的是, git status 也告訴了你應該如何作。 在最後一個例子中,未暫存區域是這樣:
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)
modified: CONTRIBUTING.md
複製代碼
它很是清楚地告訴了你如何撤消以前所作的修改。 讓咱們來按照提示執行:
$ git checkout -- CONTRIBUTING.md
$ git status
On branch master
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
renamed: README.md -> README
複製代碼
若是想查看你已經配置的遠程倉庫服務器,能夠運行 git remote 命令
你也能夠指定選項 -v ,會顯示須要讀寫遠程倉庫使用的 Git 保存的簡寫與其對應的 URL
$ git remote -v
origin https://github.com/schacon/ticgit (fetch)
origin https://github.com/schacon/ticgit (push)
複製代碼
運行 git remote add 添加一個新的遠程 Git 倉庫
$ git fetch [remote-name]
複製代碼
這個命令會訪問遠程倉庫,從中拉取全部你尚未的數據。 執行完成後,你將會擁有那個遠程倉庫中全部分支的引用,能夠隨時合併或查看。
若是你使用 clone 命令克隆了一個倉庫,命令會自動將其添加爲遠程倉庫並默認以origin 爲簡寫。 因此,git fetch origin 會抓取克隆(或上一次抓取)後新推送的全部工做。 必須注意 git fetch 命令會將數據拉取到你的本地倉庫 - 它並不會自動合併或修改你當前的工做。 當準備好時你必須手動將其合併入你的工做。
當你想分享你的項目時,必須將其推送到上游。 這個命令很簡單: git push [remote-name][branch-name] 。 當你想要將 master 分支推送到 origin 服務器時(再次說明,克隆時一般會自動幫你設置好那兩個名字),那麼運行這個命令就能夠將你所作的備份到服務器:
git push origin master
複製代碼
像其餘版本控制系統(VCS)同樣,Git 能夠給歷史中的某一個提交打上標籤,以示重要。 比較有表明性的是人們會使用這個功能來標記發佈結點(v1.0 等等)。 在本節中,你將會學習如何列出已有的標籤、如何建立新標籤、以及不一樣類型的標籤分別是什麼。
在 Git 中列出已有的標籤是很是簡單直觀的。 只須要輸入 git tag :
git tag
v0.1
v1.3
複製代碼
你也可使用特定的模式查找標籤。 例如,Git 自身的源代碼倉庫包含標籤的數量超過 500個。 若是隻對 1.8.5 系列感興趣,能夠運行:
git tag -l 'v1.8.5*'
複製代碼
Git 使用兩種主要類型的標籤:輕量標籤(lightweight)與附註標籤(annotated)。
一個輕量標籤很像一個不會改變的分支 - 它只是一個特定提交的引用。
然而,附註標籤是存儲在 Git 數據庫中的一個完整對象。 它們是能夠被校驗的;其中包含打標籤者的名字、電子郵件地址、日期時間;還有一個標籤信息;而且可使用 GNU PrivacyGuard (GPG)簽名與驗證。 一般建議建立附註標籤,這樣你能夠擁有以上全部信息;可是若是你只是想用一個臨時的標籤,或者由於某些緣由不想要保存那些信息,輕量標籤也是可用的。
在 Git 中建立一個附註標籤是很簡單的。 最簡單的方式是當你在運行 tag 命令時指定 -a選項:
$ git tag -a v1.4 -m 'my version 1.4'
$ git tag
v0.1
v1.3
v1.4
複製代碼
-m 選項指定了一條將會存儲在標籤中的信息。 若是沒有爲附註標籤指定一條信息,Git 會運行編輯器要求你輸入信息。
經過使用 git show
命令能夠看到標籤信息與對應的提交信息:
$ git show v1.4
tag v1.4
Tagger: Ben Straub <ben@straub.cc>
Date: Sat May 3 20:19:12 2014 -0700
my version 1.4
commit ca82a6dff817ec66f44342007202690a93763949
Author: Scott Chacon <schacon@gee-mail.com>
Date: Mon Mar 17 21:52:11 2008 -0700
changed the version number
複製代碼
輸出顯示了打標籤者的信息、打標籤的日期時間、附註信息,而後顯示具體的提交信息。
另外一種給提交打標籤的方式是使用輕量標籤。 輕量標籤本質上是將提交校驗和存儲到一個文件中 - 沒有保存任何其餘信息。 建立輕量標籤,不須要使用 -a 、 -s 或 -m 選項,只須要提供標籤名字:
$ git tag v1.4-lw
$ git tag
v0.1
v1.3
v1.4
v1.4-lw
v1.5
複製代碼
默認狀況下, git push 命令並不會傳送標籤到遠程倉庫服務器上。 在建立完標籤後你必須顯式地推送標籤到共享服務器上。 這個過程就像共享遠程分支同樣 - 你能夠運行 git push origin [tagname] 。
$ git push origin v1.5
Counting objects: 14, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (12/12), done.
Writing objects: 100% (14/14), 2.05 KiB | 0 bytes/s, done.
Total 14 (delta 3), reused 0 (delta 0)
To git@github.com:schacon/simplegit.git
* [new tag] v1.5 -> v1.5
複製代碼
在 Git 中你並不能真的檢出一個標籤,由於它們並不能像分支同樣來回移動。 若是你想要工做目錄與倉庫中特定的標籤版本徹底同樣,可使用 git checkout -b [branchname][tagname] 在特定的標籤上建立一個新分支:
$ git checkout -b version2 v2.0.0
Switched to a new branch 'version2'
複製代碼
固然,若是在這以後又進行了一次提交, version2 分支會由於改動向前移動了,那麼 version2 分支就會和 v2.0.0 標籤稍微有些不一樣,這時就應該小心了。
如今,你能夠完成全部基本的 Git 本地操做-建立或者克隆一個倉庫、作更改、暫存並提交這些更改、瀏覽你的倉庫從建立到如今的全部更改的歷史。 下一步,本書將介紹 Git 的殺手級特性:分支模型。
Git 保存的不是文件的變化或者差別,而是一系列不一樣時刻的文件快照。
在進行提交操做時,Git 會保存一個提交對象(commit object)。知道了 Git 保存數據的方式,咱們能夠很天然的想到——該提交對象會包含一個指向暫存內容快照的指針。但不只僅是這樣,該提交對象還包含了做者的姓名和郵箱、提交時輸入的信息以及指向它的父對象的指針。首次提交產生的提交對象沒有父對象,普通提交操做產生的提交對象有一個父對象,而由多個分支合併產生的提交對象有多個父對象,
在進行提交操做時,Git 會保存一個提交對象(commit object)。知道了 Git 保存數據的方式,咱們能夠很天然的想到——該提交對象會包含一個指向暫存內容快照的指針。 但不只僅是這樣,該提交對象還包含了做者的姓名和郵箱、提交時輸入的信息以及指向它的父對象的指針。首次提交產生的提交對象沒有父對象,普通提交操做產生的提交對象有一個父對象,而由多個分支合併產生的提交對象有多個父對象,
$ git add README test.rb LICENSE
$ git commit -m 'The initial commit of my project'
複製代碼
當使用 git commit 進行提交操做時,Git 會先計算每個子目錄(本例中只有項目根目錄)的校驗和,而後在 Git 倉庫中這些校驗和保存爲樹對象。 隨後,Git 便會建立一個提交對象,它除了包含上面提到的那些信息外,還包含指向這個樹對象(項目根目錄)的指針。如此一來,Git 就能夠在須要的時候重現這次保存的快照。
如今,Git 倉庫中有五個對象:三個 blob 對象(保存着文件快照)、一個樹對象(記錄着目錄結構和 blob 對象索引)以及一個提交對象(包含着指向前述樹對象的指針和全部提交信息)。
作些修改後再次提交,那麼此次產生的提交對象會包含一個指向上次提交對象(父對象)的指針。
Git 的分支,其實本質上僅僅是指向提交對象的可變指針。 Git 的默認分支名字是 master 。在屢次提交操做以後,你其實已經有一個指向最後那個提交對象的 master 分支。 它會在每次的提交操做中自動向前移動。
Git 是怎麼建立新分支的呢? 很簡單,它只是爲你建立了一個能夠移動的新的指針。 好比,建立一個 testing 分支, 你須要使用 git branch 命令:
$ git branch testing
複製代碼
那麼,Git 又是怎麼知道當前在哪個分支上呢? 也很簡單,它有一個名爲 HEAD 的特殊指針。 請注意它和許多其它版本控制系統(如 Subversion 或 CVS)裏的 HEAD 概念徹底不一樣。 在 Git 中,它是一個指針,指向當前所在的本地分支(譯註:將 HEAD 想象爲當前分支的別名)。 在本例中,你仍然在 master 分支上。 由於 git branch 命令僅僅 建立 一個新分支,並不會自動切換到新分支中去。
你能夠簡單地使用 git log 命令查看各個分支當前所指的對象。 提供這一功能的參數是 -- decorate 。
$ git log --oneline --decorate
f30ab (HEAD, master, testing) add feature #32 - ability to add new
34ac2 fixed bug #1328 - stack overflow under certain conditions
98ca9 initial commit of my project
複製代碼
要切換到一個已存在的分支,你須要使用 git checkout 命令。 咱們如今切換到新建立的 testing 分支去:
git checkout testing
複製代碼
這樣 HEAD 就指向 testing 分支了。
那麼,這樣的實現方式會給咱們帶來什麼好處呢? 如今不妨再提交一次:
$ vim test.rb
$ git commit -a -m 'made a change'
複製代碼
如圖所示,你的 testing 分支向前移動了,可是 master 分支卻沒有,它仍然指向運行 git checkout 時所指的對象。 這就有意思了,如今咱們切換回 master 分支看看:
$ git checkout master
複製代碼
這條命令作了兩件事。 一是使 HEAD 指回 master 分支,二是將工做目錄恢復成 master 分支所指向的快照內容。 也就是說,你如今作修改的話,項目將始於一個較舊的版本。 本質上來說,這就是忽略 testing 分支所作的修改,以便於向另外一個方向進行開發。
你能夠簡單地使用 git log 命令查看分叉歷史。 運行 git log --oneline --decorate --graph --all ,它會輸出你的提交歷史、各個分支的指向以及項目的分支分叉狀況。
$ git log --oneline --decorate --graph --all
* c2b9e (HEAD, master) made other changes
| * 87ab2 (testing) made a change
|/
* f30ab add feature #32 - ability to add new formats to the
* 34ac2 fixed bug #1328 - stack overflow under certain conditions
* 98ca9 initial commit of my project
複製代碼
遠程引用是對遠程倉庫的引用(指針),包括分支、標籤等等。 你能夠經過 git ls-remote (remote) 來顯式地得到遠程引用的完整列表,或者經過 git remote show (remote) 得到遠程分支的更多信息。 然而,一個更常見的作法是利用遠程跟蹤分支。 遠程跟蹤分支是遠程分支狀態的引用。 它們是你不能移動的本地引用,當你作任何網絡通訊操做時,它們會自動移動。 遠程跟蹤分支像是你上次鏈接到遠程倉庫時,那些分支所處狀態的書籤。 它們以 (remote)/(branch) 形式命名。 例如,若是你想要看你最後一次與遠程倉庫 origin 通訊時 master 分支的狀態,你能夠查看 origin/master 分支。 你與同事合做解決一個問題而且他們推送了一個 iss53 分支,你可能有本身的本地 iss53 分支;可是在服務器上的分支會指向 origin/iss53 的提交。
若是你在本地的 master 分支作了一些工做,然而在同一時間,其餘人推送提交到 git.ourcompany.com 並更新了它的 master 分支,那麼你的提交歷史將向不一樣的方向前進。 也許,只要你不與 origin 服務器鏈接,你的 origin/master 指針就不會移動。
若是要同步你的工做,運行 git fetch origin 命令。 這個命令查找 origin'' 是哪個服務器(在本例中,它是
git.ourcompany.com ),從中抓取本地沒有的數據,而且更新本地數據庫,移動 origin/master 指針指向新的、更新後的位置。
當你想要公開分享一個分支時,須要將其推送到有寫入權限的遠程倉庫上。 本地的分支並不會自動與遠程倉庫同步 - 你必須顯式地推送想要分享的分支。 這樣,你就能夠把不肯意分享的內容放到私人分支上,而將須要和別人協做的內容推送到公開分支。若是但願和別人一塊兒在名爲 serverfix 的分支上工做,你能夠像推送第一個分支那樣推送它。 運行 git push (remote) (branch) :
$ git push origin serverfix
複製代碼
這裏有些工做被簡化了。 Git 自動將 serverfix 分支名字展開爲 refs/heads/serverfix:refs/heads/serverfix ,那意味着, 推送本地的 serverfix 分支來更新遠程倉庫上的 serverfix 分支。'' 咱們將會詳細學習 [_git_internals] 的 refs/heads/ 部分,可是如今能夠先把它放在兒。 你也能夠運行 git push origin serverfix:serverfix,它會作一樣的事 - 至關於它說, 推送本地的 serverfix 分支,將其做爲遠程倉庫的 serverfix 分支'' 能夠經過這種格式來推送本地分支到一個命名不相同的遠程分支。 若是並不想讓遠程倉庫上的分支叫作 serverfix ,能夠運行git push origin serverfix:awesomebranch 來將本地的 serverfix 分支推送到遠程倉庫上的awesomebranch 分支。
如何避免每次輸入密碼 若是你正在使用 HTTPS URL 來推送,Git 服務器會詢問用戶名與密碼。 默認情 況下它會在終端中提示服務器是否容許你進行推送。 若是不想在每一次推送時都輸入用戶名與密碼,你能夠設置一個
credential cache''。 最簡單的方式就是將其保存在內存中幾分鐘,能夠簡單地運行
git config --global credential.helper cache 來設置它。 想要了解更多關於不一樣驗證緩存的可用選項
從一個遠程跟蹤分支檢出一個本地分支會自動建立一個叫作 跟蹤分支''(有時候也叫作 上游分支'')。 跟蹤分支是與遠程分支有直接關係的本地分支。 若是在一個跟蹤分支上輸入 git pull ,Git 能自動地識別去哪一個服務器上抓取、合併到哪一個分支。
當克隆一個倉庫時,它一般會自動地建立一個跟蹤 origin/master 的 master 分支。 然而,若是你願意的話能夠設置其餘的跟蹤分支 - 其餘遠程倉庫上的跟蹤分支,或者不跟蹤 master分支。 最簡單的就是以前看到的例子,運行 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'
複製代碼
設置已有的本地分支跟蹤一個剛剛拉取下來的遠程分支,或者想要修改正在跟蹤的上游分 支,你能夠在任意時間使用 -u 或 --set-upstream-to 選項運行 git branch 來顯式地設 置。
$ git branch -u origin/serverfix
Branch serverfix set up to track remote branch serverfix from origin.
複製代碼
若是想要查看設置的全部跟蹤分支,可使用 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
testing 5ea463a trying something new
複製代碼
當 git fetch 命令從服務器上抓取本地沒有的數據時,它並不會修改工做目錄中的內容。 它只會獲取數據而後讓你本身合併。 然而,有一個命令叫做 git pull 在大多數狀況下它的含義是一個 git fetch 緊接着一個 git merge 命令。 若是有一個像以前章節中演示的設置好的跟蹤分支,無論它是顯式地設置仍是經過 clone 或 checkout 命令爲你建立的, git pull 都會查找當前分支所跟蹤的服務器與分支,從服務器上抓取數據而後嘗試合併入那個遠程分支。 因爲 git pull 的魔法常常使人困惑因此一般單獨顯式地使用 fetch 與 merge 命令會更好 一些。
假設你已經經過遠程分支作完全部的工做了 - 也就是說你和你的協做者已經完成了一個特性而且將其合併到了遠程倉庫的 master 分支(或任何其餘穩定代碼分支)。 能夠運行帶有 --delete 選項的 git push 命令來刪除一個遠程分支。 若是想要從服務器上刪serverfix分支,運行下面的命令:
$ git push origin --delete serverfix
To https://github.com/schacon/simplegit
- [deleted] serverfix
複製代碼
在 Git 中整合來自不一樣分支的修改主要有兩種方法: merge 以及 rebase 。 在本節中咱們將學習什麼是「變基」,怎樣使用「變基」,並將展現該操做的驚豔之處,以及指出在何種狀況下你應避免使用它。
請回顧以前在 [_basic_merging] 中的一個例子,你會看到開發任務分叉到兩個不一樣分支,又各自提交了更新。
以前介紹過,整合分支最容易的方法是 merge 命令。 它會把兩個分支的最新快照( C3 和 C4 )以及兩者最近的共同祖先( C2 )進行三方合併,合併的結果是生成一個新的快照(並提交)。
其實,還有一種方法:你能夠提取在 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 , 最後以此將以前另存爲臨時文件的修改依序應用。(譯註:寫明瞭 commit id,以便理解,下同)
如今回到 master 分支,進行一次快進合併。
$ git checkout master
$ git merge experiment
複製代碼
此時, C4' 指向的快照就和上面使用 merge 命令的例子中 C5 指向的快照如出一轍了。 這兩種整合方法的最終結果沒有任何區別,可是變基使得提交歷史更加整潔。 你在查看一個通過變基的分支的歷史記錄時會發現,儘管實際的開發工做是並行的,但它們看上去就像是前後串行的同樣,提交歷史是一條直線沒有分叉。
通常咱們這樣作的目的是爲了確保在向遠程分支推送時能保持提交歷史的整潔——例如向某個別人維護的項目貢獻代碼時。 在這種狀況下,你首先在本身的分支裏進行開發,當開發完成時你須要先將你的代碼變基到 origin/master 上,而後再向主項目提交修改。 這樣的話,該項目的維護者就再也不須要進行整合工做,只須要快進合併即可。
請注意,不管是經過變基,仍是經過三方合併,整合的最終結果所指向的快照始終是同樣 的,只不過提交歷史不一樣罷了。 變基是將一系列提交按照原有次序依次應用到另外一分支上,而合併是把最終結果合在一塊兒。
呃,奇妙的變基也並不是天衣無縫,要用它得遵照一條準則:
不要對在你的倉庫外有副本的分支執行變基。
變基操做的實質是丟棄一些現有的提交,而後相應地新建一些內容同樣但實際上不一樣的提 交。 若是你已經將提交推送至某個倉庫,而其餘人也已經從該倉庫拉取提交併進行了後續工做,此時,若是你用 git rebase 命令從新整理了提交併再次推送,你的同伴所以將不得再也不次將他們手頭的工做與你的提交進行整合,若是接下來你還要拉取並整合他們修改過的提交,事情就會變得一團糟。
而後,某人又向中央服務器提交了一些修改,其中還包括一次合併。 你抓取了這些在遠程分支上的修改,並將其合併到你本地的開發分支,而後你的提交歷史就會變成這樣:
接下來,這我的又決定把合併操做回滾,改用變基;繼而又用 git push --force 命令覆蓋了服務器上的提交歷史。 以後你從服務器抓取更新,會發現多出來一些新的提交。
結果就是大家兩人的處境都十分尷尬。 若是你執行 git pull 命令,你將合併來自兩條提交 歷史的內容,生成一個新的合併提交,最終倉庫會如圖所示:
此時若是你執行 git log 命令,你會發現有兩個提交的做者、日期、日誌竟然是同樣的,這會使人感到混亂。 此外,若是你將這一堆又推送到服務器上,你其實是將那些已經被變基拋棄的提交又找了回來,這會使人感到更加混亂。 很明顯對方並不想在提交歷史中看到 C4和 C6 ,由於以前就是他們把這兩個提交經過變基丟棄的。
只要你把變基命令看成是在推送前清理提交使之整潔的工具,而且只在從未推送至共用倉庫的提交上執行變基命令,你就不會有事。 假如你在那些已經被推送至共用倉庫的提交上執行變基命令,並所以丟棄了一些別人的開發所基於的提交,那你就有大麻煩了,你的同事也會所以鄙視你。
若是你或你的同事在某些情形下決意要這麼作,請必定要通知每一個人執行 git pull --rebase命令,這樣儘管不能避免傷痛,但能有所緩解。
總的原則是,只對還沒有推送或分享給別人的本地修改執行變基操做清理歷史,從不對已推送至別處的提交執行變基操做,這樣,你才能享受到兩種方式帶來的便利。
理解 reset 和 checkout 的最簡方法,就是以 Git 的思惟框架(將其做爲內容管理器)來管理三棵不一樣的樹。 樹'' 在咱們這裏的實際意思是 文件的集合'',而不是指特定的數據結構。 (在某些狀況下索引看起來並不像一棵樹,不過咱們如今的目的是用簡單的方式思考它。)
Git 做爲一個系統,是以它的通常操做來管理並操縱這三棵樹的:
樹 | 用途 |
---|---|
HEAD | 上一次提交的快照,下一次提交的父結點 |
Index | 預期的下一次提交的快照 |
Working Directory | 沙盒 |
HEAD 是當前分支引用的指針,它老是指向該分支上的最後一次提交。 這表示 HEAD 將是下 一次提交的父結點。 一般,理解 HEAD 的最簡方式,就是將它看作 你的上一次提交 的快 照。
索引是你的 預期的下一次提交。 咱們也會將這個概念引用爲 Git 的 暫存區域'',這就是當你運行
git commit 時 Git 看起來的樣子。
Git 將上一次檢出到工做目錄中的全部文件填充到索引區,它們看起來就像最初被檢出時的樣 子。 以後你會將其中一些文件替換爲新版本,接着經過 git commit 將它們轉換爲樹來用做 新的提交。
最後,你就有了本身的工做目錄。 另外兩棵樹以一種高效但並不直觀的方式,將它們的內容 存儲在 .git 文件夾中。 工做目錄會將它們解包爲實際的文件以便編輯。 你能夠把工做目錄 當作 沙盒。在你將修改提交到暫存區並記錄到歷史以前,能夠隨意更改。
Git 主要的目的是經過操縱這三棵樹來以更加連續的狀態記錄項目的快照。
讓咱們來可視化這個過程:假設咱們進入到一個新目錄,其中有一個文件。 咱們稱其爲該文 件的 v1 版本,將它標記爲藍色。 如今運行 git init ,這會建立一個 Git 倉庫,其中的 HEAD 引用指向未建立的分支( master 還不存在)。
如今咱們想要提交這個文件,因此用 git add 來獲取工做目錄中的內容,並將其複製到索引 中。
接着運行 git commit ,它首先會移除索引中的內容並將它保存爲一個永久的快照,而後建立 一個指向該快照的提交對象,最後更新 master 來指向本次提交。
此時若是咱們運行 git status ,會發現沒有任何改動,由於如今三棵樹徹底相同。 如今咱們想要對文件進行修改而後提交它。 咱們將會經歷一樣的過程;首先在工做目錄中修 改文件。 咱們稱其爲該文件的 v2 版本,並將它標記爲紅色。
若是如今運行 git status ,咱們會看到文件顯示在 Changes not staged for commit,'' 下面並 被標記爲紅色,由於該條目在索引與工做目錄之間存在不一樣。 接着咱們運行
git add 來將它暫存到索引中。
此時,因爲索引和 HEAD 不一樣,若運行 git status 的話就會看到 Changes to be committed'' 下的該文件變爲綠色 ——也就是說,如今預期的下一次提交與上一次提交不一樣。 最後,咱們運行
git commit 來完成提交。
如今運行 git status 會沒有輸出,由於三棵樹又變得相同了。 切換分支或克隆的過程也相似。 當檢出一個分支時,它會修改 HEAD 指向新的分支引用,將 索引 填充爲該次提交的快照,而後將 索引 的內容複製到 工做目錄 中。
在如下情景中觀察 reset 命令會更有意義。 爲了演示這些例子,假設咱們再次修改了 file.txt 文件並第三次提交它。 如今的歷史看起 來是這樣的:
讓咱們跟着 reset 看看它都作了什麼。 它以一種簡單可預見的方式直接操縱這三棵樹。 它 作了三個基本操做。
第 1 步:移動 HEAD
reset 作的第一件事是移動 HEAD 的指向。 這與改變 HEAD 自身不一樣( checkout 所作 的); reset 移動 HEAD 指向的分支。 這意味着若是 HEAD 設置爲 master 分支(例如, 你正在 master 分支上),運行 git reset 9e5e64a 將會使 master 指向 9e5e64a 。
不管你調用了何種形式的帶有一個提交的 reset ,它首先都會嘗試這樣作。 使用 reset -- soft ,它將僅僅停在那兒。 如今看一眼上圖,理解一下發生的事情:它本質上是撤銷了上一次 git commit 命令。 當你 在運行 git commit 時,Git 會建立一個新的提交,並移動 HEAD 所指向的分支來使其指向該 提交。 當你將它 reset 回 HEAD~ (HEAD 的父結點)時,其實就是把該分支移動回原來的 位置,而不會改變索引和工做目錄。 如今你能夠更新索引並再次運行 git commit 來完成 git commit --amend 所要作的事情了(見 [_git_amend])。
第 2 步:更新索引(--mixed)
注意,若是你如今運行 git status 的話,就會看到新的 HEAD 和以綠色標出的它和索引之 間的區別。 接下來, reset 會用 HEAD 指向的當前快照的內容來更新索引
若是指定 --mixed 選項, reset 將會在這時中止。 這也是默認行爲,因此若是沒有指定任 何選項(在本例中只是 git reset HEAD~ ),這就是命令將會中止的地方。 如今再看一眼上圖,理解一下發生的事情:它依然會撤銷一上次 提交 ,但還會 取消暫存 所 有的東西。 因而,咱們回滾到了全部 git add 和 git commit 的命令執行以前。
第 3 步:更新工做目錄(--hard)
reset 要作的的第三件事情就是讓工做目錄看起來像索引。 若是使用 --hard 選項,它將會 繼續這一步。
如今讓咱們回想一下剛纔發生的事情。 你撤銷了最後的提交、 git add 和 git commit 命令 以及工做目錄中的全部工做。
必須注意, --hard 標記是 reset 命令惟一的危險用法,它也是 Git 會真正地銷燬數據的僅 有的幾個操做之一。 其餘任何形式的 reset 調用均可以輕鬆撤消,可是 --hard 選項不 能,由於它強制覆蓋了工做目錄中的文件。 在這種特殊狀況下,咱們的 Git 數據庫中的一個 提交內還留有該文件的 v3 版本,咱們能夠經過 reflog 來找回它。可是若該文件還未提交, Git 仍會覆蓋它從而致使沒法恢復。
reset 命令會以特定的順序重寫這三棵樹,在你指定如下選項時中止:
前面講述了 reset 基本形式的行爲,不過你還能夠給它提供一個做用路徑。 若指定了一個 路徑, reset 將會跳過第 1 步,而且將它的做用範圍限定爲指定的文件或文件集合。 這樣作 天然有它的道理,由於 HEAD 只是一個指針,你沒法讓它同時指向兩個提交中各自的一部 分。 不過索引和工做目錄 能夠部分更新,因此重置會繼續進行第 二、3 步。 如今,假如咱們運行 git reset file.txt (這實際上是 git reset --mixed HEAD file.txt 的簡 寫形式,由於你既沒有指定一個提交的 SHA-1 或分支,也沒有指定 --soft 或 --hard ), 它會:
它還有 取消暫存文件 的實際效果。 若是咱們查看該命令的示意圖,而後再想一想 git add 所 作的事,就會發現它們正好相反。
這就是爲何 git status 命令的輸出會建議運行此命令來取消暫存一個文件。 (查看 [_unstaging] 來了解更多。)
咱們能夠不讓 Git 從 HEAD 拉取數據,而是經過具體指定一個提交來拉取該文件的對應版 本。 咱們只需運行相似於 git reset eb43bf file.txt 的命令便可。
它其實作了一樣的事情,也就是把工做目錄中的文件恢復到 v1 版本,運行 git add 添加 它,而後再將它恢復到 v3 版本(只是不用真的過一遍這些步驟)。 若是咱們如今運行 git commit ,它就會記錄一條「將該文件恢復到 v1 版本」的更改,儘管咱們並未在工做目錄中真正 地再次擁有它。
咱們來看看如何利用這種新的功能來作一些有趣的事情 - 壓縮提交。 假設你的一系列提交信息中有 oops.''、 WIP'' 和 forgot this file'', 聰明的你就能使用
reset 來輕鬆快速地將它們壓縮成單個提交,也顯出你的聰明。 ([_squashing] 展現了另外一 種方式,不過在本例中用 reset 更簡單。) 假設你有一個項目,第一次提交中有一個文件,第二次提交增長了一個新的文件並修改了第 一個文件,第三次提交再次修改了第一個文件。 因爲第二次提交是一個未完成的工做,所以 你想要壓縮它。
那麼能夠運行 git reset --soft HEAD~2 來將 HEAD 分支移動到一箇舊一點的提交上(即你 想要保留的第一個提交):
而後只需再次運行 git commit :
如今你能夠查看可到達的歷史,即將會推送的歷史,如今看起來有個 v1 版 file-a.txt 的提 交,接着第二個提交將 file-a.txt 修改爲了 v3 版並增長了 file-b.txt 。 包含 v2 版本的 文件已經不在歷史中了。
最後,你大概還想知道 checkout 和 reset 之間的區別。 和 reset 同樣, checkout 也操 縱三棵樹,不過它有一點不一樣,這取決於你是否傳給該命令一個文件路徑。
運行 git checkout [branch] 與運行 git reset --hard [branch] 很是類似,它會更新全部三 棵樹使其看起來像 [branch] ,不過有兩點重要的區別。
首先不一樣於 reset --hard , checkout 對工做目錄是安全的,它會經過檢查來確保不會將已 更改的文件吹走。 其實它還更聰明一些。它會在工做目錄中先試着簡單合併一下,這樣全部 還未修改過的文件都會被更新。 而 reset --hard 則會不作檢查就全面地替換全部東西。
第二個重要的區別是如何更新 HEAD。 reset 會移動 HEAD 分支的指向,而 checkout 只 會移動 HEAD 自身來指向另外一個分支。
例如,假設咱們有 master 和 develop 分支,它們分別指向不一樣的提交;咱們如今在 develop 上(因此 HEAD 指向它)。 若是咱們運行 git reset master ,那麼 develop 自身 如今會和 master 指向同一個提交。 而若是咱們運行 git checkout master 的話, develop 不會移動,HEAD 自身會移動。 如今 HEAD 將會指向 master 。 因此,雖然在這兩種狀況下咱們都移動 HEAD 使其指向了提交 A,但作法是很是不一樣的。 reset 會移動 HEAD 分支的指向,而 checkout 則移動 HEAD 自身。
運行 checkout 的另外一種方式就是指定一個文件路徑,這會像 reset 同樣不會移動 HEAD。 它就像 git reset [branch] file 那樣用該次提交中的那個文件來更新索引,可是它也會覆蓋 工做目錄中對應的文件。 它就像是 git reset --hard [branch] file (若是 reset 容許你這 樣運行的話)- 這樣對工做目錄並不安全,它也不會移動 HEAD。
從根本上來說 Git是一個內容尋址(content-addressable)文件系統,並在此之上提供了一個版本控制系統的用戶界面。
本書旨在討論如何經過 checkout 、 branch 、 remote 等大約 30 個諸如此類動詞形式的命令 來玩轉 Git。 然而,因爲 Git 最初是一套面向版本控制系統的工具集,而不是一個完整的、用 戶友好的版本控制系統,因此它還包含了一部分用於完成底層工做的命令。 這些命令被設計 成能以 UNIX 命令行的風格鏈接在一塊兒,抑或藉由腳本調用,來完成工做。 這部分命令通常 被稱做「底層(plumbing)」命令,而那些更友好的命令則被稱做「高層(porcelain)」命令。 本書前九章專一於探討高層命令。 然而在本章,咱們將主要面對底層命令。 由於,底層命令 得以讓你窺探 Git 內部的工做機制,也有助於說明 Git 是如何完成工做的,以及它爲什麼如此運 做。 多數底層命令並不面向最終用戶:它們更適合做爲新命令和自定義腳本的組成部分。
當在一個新目錄或已有目錄執行 git init 時,Git 會建立一個 .git 目錄。 這個目錄包含 了幾乎全部 Git 存儲和操做的對象。 如若想備份或複製一個版本庫,只需把這個目錄拷貝至 另外一處便可。 本章探討的全部內容,均位於這個目錄內。 該目錄的結構以下所示:
$ ls -F1
HEAD
config*
description
hooks/
info/
objects/
refs/
複製代碼
description 文件僅供 GitWeb 程序使用,咱們無需關心。 config 文件包含項目特有的配置選項。 info 目錄包含一個全局性排除(global exclude)文件,用以放置那些不但願被記錄在 .gitignore 文件中的忽略模式(ignored patterns)。 hooks 目錄包含客戶端或服務端的鉤子腳本(hook scripts),在 [_git_hooks] 中這部分話題已被詳細探討過。
剩下的四個條目很重要: HEAD 文件、(尚待建立的) index 文件,和 objects 目 錄、 refs 目錄。 這些條目是 Git 的核心組成部分。 objects 目錄存儲全部數據內 容; refs 目錄存儲指向數據(分支)的提交對象的指針; HEAD 文件指示目前被檢出的分 支; index 文件保存暫存區信息。 咱們將詳細地逐一檢視這四部分,以期理解 Git 是如何運 轉的。
Git 是一個內容尋址文件系統。 看起來很酷, 但這是什麼意思呢? 這意味着,Git 的核心部 分是一個簡單的鍵值對數據庫(key-value data store)。 你能夠向該數據庫插入任意類型的 內容,它會返回一個鍵值,經過該鍵值能夠在任意時刻再次檢索(retrieve)該內容。 能夠通 過底層命令 hash-object 來演示上述效果——該命令可將任意數據保存於 .git 目錄,並返 回相應的鍵值。 首先,咱們須要初始化一個新的 Git 版本庫,並確認 objects 目錄爲空:
$ git init test
Initialized empty Git repository in /tmp/test/.git/
$ cd test
$ find .git/objects
.git/objects
.git/objects/info
.git/objects/pack
$ find .git/objects -type f
複製代碼
能夠看到 Git 對 objects 目錄進行了初始化,並建立了 pack 和 info 子目錄,但均爲空。 接着,往 Git 數據庫存入一些文本:
$ echo 'test content' | git hash-object -w --stdin
d670460b4b4aece5915caf5c68d12f560a9fe3e4
複製代碼
-w 選項指示 hash-object 命令存儲數據對象;若不指定此選項,則該命令僅返回對應的鍵 值。 --stdin 選項則指示該命令從標準輸入讀取內容;若不指定此選項,則須在命令尾部給 出待存儲文件的路徑。 該命令輸出一個長度爲 40 個字符的校驗和。 這是一個 SHA-1 哈希值 ——一個將待存儲的數據外加一個頭部信息(header)一塊兒作 SHA-1 校驗運算而得的校驗 和。後文會簡要討論該頭部信息。 如今咱們能夠查看 Git 是如何存儲數據的:
$ find .git/objects -type f
.git/objects/d6/70460b4b4aece5915caf5c68d12f560a9fe3e4
複製代碼
能夠在 objects 目錄下看到一個文件。 這就是開始時 Git 存儲內容的方式——一個文件對應 一條內容,以該內容加上特定頭部信息一塊兒的 SHA-1 校驗和爲文件命名。 校驗和的前兩個字 符用於命名子目錄,餘下的 38 個字符則用做文件名。 能夠經過 cat-file 命令從 Git 那裏取回數據。 這個命令簡直就是一把剖析 Git 對象的瑞士 軍刀。 爲 cat-file 指定 -p 選項可指示該命令自動判斷內容的類型,併爲咱們顯示格式友 好的內容:
$ git cat-file -p d670460b4b4aece5915caf5c68d12f560a9fe3e4
test content
複製代碼
接下來要探討的對象類型是樹對象(tree object),它能解決文件名保存的問題,也容許咱們 將多個文件組織到一塊兒。 Git 以一種相似於 UNIX 文件系統的方式存儲內容,但做了些許簡 化。 全部內容均以樹對象和數據對象的形式存儲,其中樹對象對應了 UNIX 中的目錄項,數 據對象則大體上對應了 inodes 或文件內容。 一個樹對象包含了一條或多條樹對象記錄(tree entry),每條記錄含有一個指向數據對象或者子樹對象的 SHA-1 指針,以及相應的模式、類 型、文件名信息。 例如,某項目當前對應的最新樹對象多是這樣的:
$ git cat-file -p master^{tree}
100644 blob a906cb2a4a904a152e80877d4088654daad0c859 README
100644 blob 8f94139338f9404f26296befa88755fc2598c289 Rakefile
040000 tree 99f1a6d12cb4b6f19c8655fca46c3ecf317074e0 lib
複製代碼
master^{tree} 語法表示 master 分支上最新的提交所指向的樹對象。 請注意, lib 子目 錄(所對應的那條樹對象記錄)並非一個數據對象,而是一個指針,其指向的是另外一個樹 對象:
$ git cat-file -p 99f1a6d12cb4b6f19c8655fca46c3ecf317074e0
100644 blob 47c6340d6459e05787f644c2447d2595f5d3a54b simplegit.rb
複製代碼
從概念上講,Git 內部存儲的數據有點像這樣:
如今有三個樹對象,分別表明了咱們想要跟蹤的不一樣項目快照。然而問題依舊:若想重用這 些快照,你必須記住全部三個 SHA-1 哈希值。 而且,你也徹底不知道是誰保存了這些快照, 在什麼時刻保存的,以及爲何保存這些快照。 而以上這些,正是提交對象(commit object)能爲你保存的基本信息。
能夠經過調用 commit-tree 命令建立一個提交對象,爲此須要指定一個樹對象的 SHA-1 值, 以及該提交的父提交對象(若是有的話)。 咱們從以前建立的第一個樹對象開始
$ echo 'first commit' | git commit-tree d8329f
fdf4fc3344e67ab068f836878b6c4951e3b15f3d
複製代碼
如今能夠經過 cat-file 命令查看這個新提交對象:
$ 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
複製代碼
接着,咱們將建立另兩個提交對象,它們分別引用各自的上一個提交(做爲其父提交對 象):
$ echo 'second commit' | git commit-tree 0155eb -p fdf4fc3
cac0cab538b970a37ea1e769cbbde608743bc96d
$ echo 'third commit' | git commit-tree 3c4e9c -p cac0cab
1a410efbd13591db07496601ebc7a059dd55cfe9
複製代碼
這三個提交對象分別指向以前建立的三個樹對象快照中的一個。 如今,若是對最後一個提交 的 SHA-1 值運行 git log 命令,會出乎意料的發現,你已有一個貨真價實的、可由 git log 查看的 Git 提交歷史了:
$ 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 file changed, 1 insertion(+)
commit cac0cab538b970a37ea1e769cbbde608743bc96d
Author: Scott Chacon <schacon@gmail.com>
Date: Fri May 22 18:14:29 2009 -0700
second commit
new.txt | 1 +
test.txt | 2 +-
2 files changed, 2 insertions(+), 1 deletion(-)
commit fdf4fc3344e67ab068f836878b6c4951e3b15f3d
Author: Scott Chacon <schacon@gmail.com>
Date: Fri May 22 18:09:34 2009 -0700
first commit
test.txt | 1 +
1 file changed, 1 insertion(+)
複製代碼
太神奇了: 就在剛纔,你沒有藉助任何上層命令,僅憑几個底層操做便完成了一個 Git 提交 歷史的建立。 這就是每次咱們運行 git add 和 git commit 命令時, Git 所作的實質工做
——將被改寫的文件保存爲數據對象,更新暫存區,記錄樹對象,最後建立一個指明瞭頂層
樹對象和父提交的提交對象。
這三種主要的 Git 對象——數據對象、樹對象、提交對象—— 最初均以單獨文件的形式保存在 .git/objects 目錄下。 下面列出了目前示例目錄內的全部 對象,輔以各自所保存內容的註釋:
$ 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
複製代碼
若是跟蹤全部的內部指針,將獲得一個相似下面的對象關係圖:
前文曾說起,在存儲內容時,會有個頭部信息一併被保存。 讓咱們略花些時間來看看 Git 是 如何存儲其對象的。 經過在 Ruby 腳本語言中交互式地演示,你將看到一個數據對象——本 例中是字符串「what is up, doc?」——是如何被存儲的。 能夠經過 irb 命令啓動 Ruby 的交互模式:
$ irb
>> content = "what is up, doc?"
=> "what is up, doc?"
複製代碼
Git 以對象類型做爲開頭來構造一個頭部信息,本例中是一個「blob」字符串。 接着 Git 會添加 一個空格,隨後是數據內容的長度,最後是一個空字節(null byte):
>> header = "blob #{content.length}\0"
=> "blob 16\u0000"
複製代碼
Git 會將上述頭部信息和原始數據拼接起來,並計算出這條新內容的 SHA-1 校驗和。 在 Ruby 中能夠這樣計算 SHA-1 值——先經過require 命令導入 SHA-1 digest 庫,而後對目 標字符串調用 Digest::SHA1.hexdigest() :
>> store = header + content
=> "blob 16\u0000what is up, doc?"
>> require 'digest/sha1'
=> true
>> sha1 = Digest::SHA1.hexdigest(store)
=> "bd9dbf5aae1a3862dd1526723246b20206e5fc37"
複製代碼
Git 會經過 zlib 壓縮這條新內容。在 Ruby 中能夠藉助 zlib 庫作到這一點。 先導入相應的庫, 而後對目標內容調用 Zlib::Deflate.deflate() :
>> require 'zlib'
=> true
>> zlib_content = Zlib::Deflate.deflate(store)
=> "x\x9CK\xCA\xC9OR04c(\xCFH,Q\xC8,V(-\xD0QH\xC9O\xB6\a\x00_\x1C\a\x9D"
複製代碼
最後,須要將這條經由 zlib 壓縮的內容寫入磁盤上的某個對象。 要先肯定待寫入對象的路徑 (SHA-1 值的前兩個字符做爲子目錄名稱,後 38 個字符則做爲子目錄內文件的名稱)。 如 果該子目錄不存在,能夠經過 Ruby 中的 FileUtils.mkdir_p() 函數來建立它。 接着,經過 File.open() 打開這個文件。最後,對上一步中獲得的文件句柄調用 write() 函數,以向目 標文件寫入以前那條 zlib 壓縮過的內容:
>> path = '.git/objects/' + sha1[0,2] + '/' + sha1[2,38]
=> ".git/objects/bd/9dbf5aae1a3862dd1526723246b20206e5fc37"
>> require 'fileutils'
=> true
>> FileUtils.mkdir_p(File.dirname(path))
=> ".git/objects/bd"
>> File.open(path, 'w') { |f| f.write zlib_content }
=> 32
複製代碼
就是這樣——你已建立了一個有效的 Git 數據對象。 全部的 Git 對象均以這種方式存儲,區 別僅在於類型標識——另兩種對象類型的頭部信息以字符串「commit」或「tree」開頭,而不 是「blob」。 另外,雖然數據對象的內容幾乎能夠是任何東西,但提交對象和樹對象的內容卻有 各自固定的格式。
咱們能夠藉助相似於 git log 1a410e 這樣的命令來瀏覽完整的提交歷史,但爲了能遍歷那段 歷史從而找到全部相關對象,你仍須記住 1a410e 是最後一個提交。 咱們須要一個文件來保 存 SHA-1 值,並給文件起一個簡單的名字,而後用這個名字指針來替代原始的 SHA-1 值。
在 Git 裏,這樣的文件被稱爲「引用(references,或縮寫爲 refs)」;你能夠在 .git/refs 目 錄下找到這類含有 SHA-1 值的文件。 在目前的項目中,這個目錄沒有包含任何文件,但它包 含了一個簡單的目錄結構:
$ find .git/refs
.git/refs
.git/refs/heads
.git/refs/tags
$ 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 分支的本質:一個指向某一系列提交之首的指針或引用。 若想在第二個提交 上建立一個分支,能夠這麼作:
$ git update-ref refs/heads/test cac0ca
複製代碼
這個分支將只包含從第二個提交開始往前追溯的記錄:
$ git log --pretty=oneline test
cac0cab538b970a37ea1e769cbbde608743bc96d second commit
fdf4fc3344e67ab068f836878b6c4951e3b15f3d first commit
複製代碼
至此,咱們的 Git 數據庫從概念上看起來像這樣:
當運行相似於 git branch (branchname) 這樣的命令時,Git 實際上會運行 update-ref 命 令,取得當前所在分支最新提交對應的 SHA-1 值,並將其加入你想要建立的任何新引用中。
如今的問題是,當你執行 git branch (branchname) 時,Git 如何知道最新提交的 SHA-1 值 呢? 答案是 HEAD 文件。
HEAD 文件是一個符號引用(symbolic reference),指向目前所在的分支。 所謂符號引用, 意味着它並不像普通引用那樣包含一個 SHA-1 值——它是一個指向其餘引用的指針。 若是查 看 HEAD 文件的內容,通常而言咱們看到的相似這樣:
$ cat .git/HEAD
ref: refs/heads/master
複製代碼
若是執行 git checkout test ,Git 會像這樣更新 HEAD 文件:
$ cat .git/HEAD
ref: refs/heads/test
複製代碼
當咱們執行 git commit 時,該命令會建立一個提交對象,並用 HEAD 文件中那個引用所指 向的 SHA-1 值設置其父提交字段。
前文咱們剛討論過 Git 的三種主要對象類型,事實上還有第四種。 標籤對象(tag object)非 常相似於一個提交對象——它包含一個標籤建立者信息、一個日期、一段註釋信息,以及一 個指針。 主要的區別在於,標籤對象一般指向一個提交對象,而不是一個樹對象。 它像是一 個永不移動的分支引用——永遠指向同一個提交對象,只不過給這個提交對象加上一個更友 好的名字罷了。 正如 [_git_basics_chapter] 中所討論的那樣,存在兩種類型的標籤:附註標籤和輕量標籤。 能夠像這樣建立一個輕量標籤:
$ git update-ref refs/tags/v1.0 cac0cab538b970a37ea1e769cbbde608743bc96d
複製代碼
這就是輕量標籤的所有內容——一個固定的引用。 然而,一個附註標籤則更復雜一些。 若要 建立一個附註標籤,Git 會建立一個標籤對象,並記錄一個引用來指向該標籤對象,而不是直 接指向提交對象。 能夠經過建立一個附註標籤來驗證這個過程( -a 選項指定了要建立的是 一個附註標籤):
$ git tag -a v1.1 1a410efbd13591db07496601ebc7a059dd55cfe9 -m 'test tag'
複製代碼
下面是上述過程所建標籤對象的 SHA-1 值:
$ cat .git/refs/tags/v1.1
9585191f37f7b0fb9444f35a9bf50de191beadc2
複製代碼
如今對該 SHA-1 值運行 cat-file 命令:
$ git cat-file -p 9585191f37f7b0fb9444f35a9bf50de191beadc2
object 1a410efbd13591db07496601ebc7a059dd55cfe9
type commit
tag v1.1
tagger Scott Chacon <schacon@gmail.com> Sat May 23 16:48:58 2009 -0700
test tag
複製代碼
咱們注意到,object 條目指向咱們打了標籤的那個提交對象的 SHA-1 值。 另外要注意的是, 標籤對象並不是必須指向某個提交對象;你能夠對任意類型的 Git 對象打標籤。
咱們將看到的第三種引用類型是遠程引用(remote reference)。 若是你添加了一個遠程版本 庫並對其執行過推送操做,Git 會記錄下最近一次推送操做時每個分支所對應的值,並保存 在 refs/remotes 目錄下。 例如,你能夠添加一個叫作 origin 的遠程版本庫,而後把 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 文件,能夠發現 origin 遠程版本庫的 master 分支所對應的 SHA-1 值,就是最近一次與服務器通訊時本地 master 分支所對應的 SHA-1 值:
$ cat .git/refs/remotes/origin/master
ca82a6dff817ec66f44342007202690a93763949
複製代碼
遠程引用和分支(位於 refs/heads 目錄下的引用)之間最主要的區別在於,遠程引用是隻 讀的。 雖然能夠 git checkout 到某個遠程引用,可是 Git 並不會將 HEAD 引用指向該遠程 引用。所以,你永遠不能經過 commit 命令來更新遠程引用。 Git 將這些遠程引用做爲記錄 遠程服務器上各分支最後已知位置狀態的書籤來管理。
讓咱們從新回到示例 Git 版本庫的對象數據庫。 目前爲止,能夠看到有 11 個對象——4 個數 據對象、3 個樹對象、3 個提交對象和 1 個標籤對象:
$ 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/95/85191f37f7b0fb9444f35a9bf50de191beadc2 # tag
.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
複製代碼
Git 使用 zlib 壓縮這些文件的內容,並且咱們並無存儲太多東西,因此上文中的文件一共只 佔用了 925 字節。 接下來,咱們會指引你添加一些大文件到版本庫中,以此展現 Git 的一個 頗有趣的功能。 爲了便於展現,咱們要把以前在 Grit 庫中用到過的 repo.rb 文件添加進來 ——這是一個大小約爲 22K 的源代碼文件:
$ curl https://raw.githubusercontent.com/mojombo/grit/master/lib/grit/repo.rb > repo.r
b
$ git add repo.rb
$ git commit -m 'added repo.rb'
[master 484a592] added repo.rb
3 files changed, 709 insertions(+), 2 deletions(-)
delete mode 100644 bak/test.txt
create mode 100644 repo.rb
rewrite test.txt (100%)
複製代碼
若是你查看生成的樹對象,能夠看到 repo.rb 文件對應的數據對象的 SHA-1 值:
$ git cat-file -p master^{tree}
100644 blob fa49b077972391ad58037050f2a75f74e3671e92 new.txt
100644 blob 033b4468fa6b2a9547a70d88d1bbe8bf3f9ed0d5 repo.rb
100644 blob e3f094f522629ae358806b17daf78246c27c007b test.txt
複製代碼
接下來你可使用 git cat-file 命令查看這個對象有多大:
$ git cat-file -s 033b4468fa6b2a9547a70d88d1bbe8bf3f9ed0d5
22044
複製代碼
如今,稍微修改這個文件,而後看看會發生什麼:
$ echo '# testing' >> repo.rb
$ git commit -am 'modified repo a bit'
[master 2431da6] modified repo.rb a bit
1 file changed, 1 insertion(+)
複製代碼
查看這個提交生成的樹對象,你會看到一些有趣的東西:
$ git cat-file -p master^{tree}
100644 blob fa49b077972391ad58037050f2a75f74e3671e92 new.txt
100644 blob b042a60ef7dff760008df33cee372b945b6e884e repo.rb
100644 blob e3f094f522629ae358806b17daf78246c27c007b test.txt
複製代碼
repo.rb 對應一個與以前徹底不一樣的數據對象,這意味着,雖然你只是在一個 400 行的文件後 面加入一行新內容,Git 也會用一個全新的對象來存儲新的文件內容:
$ git cat-file -s b042a60ef7dff760008df33cee372b945b6e884e
22054
複製代碼
你的磁盤上如今有兩個幾乎徹底相同、大小均爲 22K 的對象。 若是 Git 只完整保存其中一 個,再保存另外一個對象與以前版本的差別內容,豈不更好? 事實上 Git 能夠那樣作。 Git 最初向磁盤中存儲對象時所使用的格式被稱爲「鬆散(loose)」對 象格式。 可是,Git 會時不時地將多個這些對象打包成一個稱爲「包文件(packfile)」的二進制 文件,以節省空間和提升效率。 當版本庫中有太多的鬆散對象,或者你手動執行 git gc 命 令,或者你向遠程服務器執行推送時,Git 都會這樣作。 要看到打包過程,你能夠手動執行 git gc 命令讓 Git 對對象進行打包:
$ git gc
Counting objects: 18, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (14/14), done.
Writing objects: 100% (18/18), done.
Total 18 (delta 3), reused 0 (delta 0)
複製代碼
這個時候再查看 objects 目錄,你會發現大部分的對象都不見了,與此同時出現了一對新文 件:
$ find .git/objects -type f
.git/objects/bd/9dbf5aae1a3862dd1526723246b20206e5fc37
.git/objects/d6/70460b4b4aece5915caf5c68d12f560a9fe3e4
.git/objects/info/packs
.git/objects/pack/pack-978e03944f5c581011e6998cd0e9e30000905586.idx
.git/objects/pack/pack-978e03944f5c581011e6998cd0e9e30000905586.pack
複製代碼
仍保留着的幾個對象是未被任何提交記錄引用的數據對象——在此例中是你以前建立的「what is up, doc?」和「test content」這兩個示例數據對象。 由於你從沒將它們添加至任何提交記錄 中,因此 Git 認爲它們是搖擺(dangling)的,不會將它們打包進新生成的包文件中。 剩下的文件是新建立的包文件和一個索引。 包文件包含了剛纔從文件系統中移除的全部對象 的內容。 索引文件包含了包文件的偏移信息,咱們經過索引文件就能夠快速定位任意一個指 定對象。 有意思的是運行 gc 命令前磁盤上的對象大小約爲 22K,而這個新生成的包文件大 小僅有 7K。 經過打包對象減小了 ⅔ 的磁盤佔用空間。 Git 是如何作到這點的? Git 打包對象時,會查找命名及大小相近的文件,並只保存文件不一樣 版本之間的差別內容。 你能夠查看包文件,觀察它是如何節省空間的。 git verify-pack 這 個底層命令可讓你查看已打包的內容:
$ git verify-pack -v .git/objects/pack/pack-978e03944f5c581011e6998cd0e9e30000905586.i
dx
2431da676938450a4d72e260db3bf7b0f587bbc1 commit 223 155 12
69bcdaff5328278ab1c0812ce0e07fa7d26a96d7 commit 214 152 167
80d02664cb23ed55b226516648c7ad5d0a3deb90 commit 214 145 319
43168a18b7613d1281e5560855a83eb8fde3d687 commit 213 146 464
092917823486a802e94d727c820a9024e14a1fc2 commit 214 146 610
702470739ce72005e2edff522fde85d52a65df9b commit 165 118 756
d368d0ac0678cbe6cce505be58126d3526706e54 tag 130 122 874
fe879577cb8cffcdf25441725141e310dd7d239b tree 136 136 996
d8329fc1cc938780ffdd9f94e0d364e0ea74f579 tree 36 46 1132
deef2e1b793907545e50a2ea2ddb5ba6c58c4506 tree 136 136 1178
d982c7cb2c2a972ee391a85da481fc1f9127a01d tree 6 17 1314 1 \
deef2e1b793907545e50a2ea2ddb5ba6c58c4506
3c4e9cd789d88d8d89c1073707c3585e41b0e614 tree 8 19 1331 1 \
deef2e1b793907545e50a2ea2ddb5ba6c58c4506
0155eb4229851634a0f03eb265b69f5a2d56f341 tree 71 76 1350
83baae61804e65cc73a7201a7252750c76066a30 blob 10 19 1426
fa49b077972391ad58037050f2a75f74e3671e92 blob 9 18 1445
b042a60ef7dff760008df33cee372b945b6e884e blob 22054 5799 1463
033b4468fa6b2a9547a70d88d1bbe8bf3f9ed0d5 blob 9 20 7262 1 \
b042a60ef7dff760008df33cee372b945b6e884e
1f7a7a472abf3dd9643fd615f6da379c4acb3e3a blob 10 19 7282
non delta: 15 objects
chain length = 1: 3 objects
.git/objects/pack/pack-978e03944f5c581011e6998cd0e9e30000905586.pack: ok
複製代碼
此處, 033b4 這個數據對象(即 repo.rb 文件的第一個版本,若是你還記得的話)引用了數 據對象 b042a ,即該文件的第二個版本。 命令輸出內容的第三列顯示的是各個對象在包文件 中的大小,能夠看到 b042a 佔用了 22K 空間,而 033b4 僅佔用 9 字節。 一樣有趣的地方 在於,第二個版本完整保存了文件內容,而原始的版本反而是以差別方式保存的——這是因 爲大部分狀況下須要快速訪問文件的最新版本。 最妙之處是你能夠隨時從新打包。 Git 時常會自動對倉庫進行從新打包以節省空間。固然你也 能夠隨時手動執行 git gc 命令來這麼作。
縱觀全書,咱們已經使用過一些諸如遠程分支到本地引用的簡單映射方式,但這種映射能夠更復雜。 假設你添加了這樣一個遠程版本庫:
$ git remote add origin https://github.com/schacon/simplegit-progit
複製代碼
上述命令會在你的 .git/config 文件中添加一個小節,並在其中指定遠程版本庫的名稱 ( origin )、URL 和一個用於獲取操做的引用規格(refspec):
[remote "origin"]
url = https://github.com/schacon/simplegit-progit
fetch = +refs/heads/*:refs/remotes/origin/*
複製代碼
引用規格的格式由一個可選的 + 號和緊隨其後的 : 組成,其中 是一個 模式(pattern),表明遠程版本庫中的引用; 是那些遠程引用在本地所對應的位置。
、+號告訴 Git 即便在不能快進的狀況下也要(強制)更新引用。
默認狀況下,引用規格由 git remote add 命令自動生成, Git 獲取服務器中 refs/heads/ 下面的全部引用,並將它寫入到本地的 refs/remotes/origin/ 中。 因此,若是服務器上有一 個 master 分支,咱們能夠在本地經過下面這種方式來訪問該分支上的提交記錄:
$ git log origin/master
$ git log remotes/origin/master
$ git log refs/remotes/origin/master
複製代碼
上面的三個命令做用相同,由於 Git 會把它們都擴展成 refs/remotes/origin/master 。 若是想讓 Git 每次只拉取遠程的 master 分支,而不是全部分支,能夠把(引用規格的)獲 取那一行修改成:
fetch = +refs/heads/master:refs/remotes/origin/master
複製代碼
你也能夠指定多個引用規格。 在命令行中,你能夠按照以下的方式拉取多個分支:
像上面這樣從遠程版本庫獲取已在命名空間中的引用固然很棒,但 QA 團隊最初應該如何將 他們的分支放入遠程的 qa/ 命名空間呢? 咱們能夠經過引用規格推送來完成這個任務。 若是 QA 團隊想把他們的 master 分支推送到遠程服務器的 qa/master 分支上,能夠運行:
$ git push origin master:refs/heads/qa/master
複製代碼
若是他們但願 Git 每次運行 git push origin 時都像上面這樣推送,能夠在他們的配置文件 中添加一條 push 值:
[remote "origin"]
url = https://github.com/schacon/simplegit-progit
fetch = +refs/heads/*:refs/remotes/origin/*
push = refs/heads/master:refs/heads/qa/master
複製代碼
正如剛纔所指出的,這會讓 git push origin 默認把本地 master 分支推送到遠程 qa/master 分支。
你還能夠藉助相似下面的命令經過引用規格從遠程服務器上刪除引用:
$ git push origin :topic
複製代碼
由於引用規格(的格式)是 : ,因此上述命令把 留空,意味着把遠程版本 庫的 topic 分支定義爲空值,也就是刪除它。
有的時候,你須要對倉庫進行清理 - 使它的結構變得更緊湊,或是對導入的倉庫進行清理,或 是恢復丟失的內容。 這個小節將會介紹這些狀況中的一部分。
Git 會不定時地自動運行一個叫作 auto gc'' 的命令。 大多數時候,這個命令並不會產生效果。 然而,若是有太多鬆散對象(不在包文件中的對象)或者太多包文件,Git 會運行一個完整的 git gc 命令。 gc'' 表明垃圾回收,這個命令會作如下事情:收集全部鬆散對象並將它們放置到包文件中,將多個包文件合併爲一個大的包文件,移除與任何提交都不相關的陳舊對象。 能夠像下面同樣手動執行自動垃圾回收:
$ git gc --auto
複製代碼
就像上面提到的,這個命令一般並不會產生效果。 大約須要 7000 個以上的鬆散對象或超過 50 個的包文件才能讓 Git 啓動一次真正的 gc 命令。 你能夠經過修改 gc.auto 與 gc.autopacklimit 的設置來改動這些數值。
在你使用 Git 的時候,你可能會意外丟失一次提交。 一般這是由於你強制刪除了正在工做的 分支,可是最後卻發現你還須要這個分支;亦或者硬重置了一個分支,放棄了你想要的提 交。 若是這些事情已經發生,該如何找回你的提交呢? 下面的例子將硬重置你的測試倉庫中的 master 分支到一箇舊的提交,以此來恢復丟失的提 交。 首先,讓咱們看看你的倉庫如今在什麼地方:
$ git log --pretty=oneline
ab1afef80fac8e34258ff41fc1b867c702daa24b modified repo a bit
484a59275031909e19aadb7c92262719cfcdf19a added repo.rb
1a410efbd13591db07496601ebc7a059dd55cfe9 third commit
cac0cab538b970a37ea1e769cbbde608743bc96d second commit
fdf4fc3344e67ab068f836878b6c4951e3b15f3d first commit
複製代碼
如今,咱們將 master 分支硬重置到第三次提交:
$ git reset --hard 1a410efbd13591db07496601ebc7a059dd55cfe9
HEAD is now at 1a410ef third commit
$ git log --pretty=oneline
1a410efbd13591db07496601ebc7a059dd55cfe9 third commit
cac0cab538b970a37ea1e769cbbde608743bc96d second commit
fdf4fc3344e67ab068f836878b6c4951e3b15f3d first commit
複製代碼
如今頂部的兩個提交已經丟失了 - 沒有分支指向這些提交。 你須要找出最後一次提交的 SHA- 1 而後增長一個指向它的分支。 竅門就是找到最後一次的提交的 SHA-1 - 可是估計你記不起 來了,對嗎? 最方便,也是最經常使用的方法,是使用一個名叫 git reflog 的工具。 當你正在工做時,Git 會 默默地記錄每一次你改變 HEAD 時它的值。 每一次你提交或改變分支,引用日誌都會被更 新。 引用日誌(reflog)也能夠經過 git update-ref 命令更新,咱們在 [_git_refs] 有提到使 用這個命令而不是是直接將 SHA-1 的值寫入引用文件中的緣由。 你能夠在任什麼時候候經過執行 git reflog 命令來了解你曾經作過什麼:
$ git reflog
1a410ef HEAD@{0}: reset: moving to 1a410ef
ab1afef HEAD@{1}: commit: modified repo.rb a bit
484a592 HEAD@{2}: commit: added repo.rb
複製代碼
這裏能夠看到咱們已經檢出的兩次提交,然而並無足夠多的信息。 爲了使顯示的信息更加 有用,咱們能夠執行 git log -g ,這個命令會以標準日誌的格式輸出引用日誌。
$ git log -g
commit 1a410efbd13591db07496601ebc7a059dd55cfe9
Reflog: HEAD@{0} (Scott Chacon <schacon@gmail.com>)
Reflog message: updating HEAD
Author: Scott Chacon <schacon@gmail.com>
Date: Fri May 22 18:22:37 2009 -0700
third commit
commit ab1afef80fac8e34258ff41fc1b867c702daa24b
Reflog: HEAD@{1} (Scott Chacon <schacon@gmail.com>)
Reflog message: updating HEAD
Author: Scott Chacon <schacon@gmail.com>
Date: Fri May 22 18:15:24 2009 -0700
modified repo.rb a bit
複製代碼
看起來下面的那個就是你丟失的提交,你能夠經過建立一個新的分支指向這個提交來恢復 它。 例如,你能夠建立一個名爲 recover-branch 的分支指向這個提交(ab1afef):
$ git branch recover-branch ab1afef
$ git log --pretty=oneline recover-branch
ab1afef80fac8e34258ff41fc1b867c702daa24b modified repo a bit
484a59275031909e19aadb7c92262719cfcdf19a added repo.rb
1a410efbd13591db07496601ebc7a059dd55cfe9 third commit
cac0cab538b970a37ea1e769cbbde608743bc96d second commit
fdf4fc3344e67ab068f836878b6c4951e3b15f3d first commit
複製代碼