小丁帶你走進git的世界四-重寫歷史記錄


一、git對象文件創建

開篇先補充一個知識點,就是比如我建立一個文件之后,使用git add就會生成一個git對象,但是git對象生成后可以在.git/objects里面對應,首先我們來初始化一個倉庫git init。

$ git init

然后我們來創建兩個文件文件名分別為a和b。

$ touch a b

將a文件添加到暫存區,然后再將b添加到暫存區,我們會想到這時候有兩個git對象產生,但是git對象對應.git/objects文件。

$ git add . $ find .git/objects/ .git/objects/ .git/objects/e6 .git/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391 .git/objects/info .git/objects/pack

我們來查看下.git/objects文件下面會產生幾條git對象庫。這時候超出了我們想象,我們認為對象文件也應該創建兩個但是僅僅創建了一個,這是為什么呢?那么着一個文件又指的是什么呢?

$ git cat-file -t e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 blob

上述命令查看了當前sha-1是git對象(blob)。這時候我們根本就看不出來這個a和b的存放位置。我們將暫存區的文件提交到記錄中,git commit –m “initial commit”

$ git commit -m 'initial commit' [master (root-commit) 5b077dd] initial commit 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 a create mode 100644 b

這時候我們再來看一下.git/object文件下內容,產生了一個tree對象和commit對象,這個commit對象指向了tree對象。

$ find .git/objects/ .git/objects/ .git/objects/29 .git/objects/29/6e56023cdc034d2735fee8c0d85a659d1b07f4 .git/objects/5b .git/objects/5b/077ddc03fa28dff8a1ac0d85969fcc2ac40c0b .git/objects/e6 .git/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391 .git/objects/info .git/objects/pack

分別查看下各自的對象類型:

$ git cat-file -t 296e56023cdc034d2735fee8c0d85a659d1b07f4 Tree $ git cat-file -t 5b077ddc03fa28dff8a1ac0d85969fcc2ac40c0b Commit $ git cat-file -t e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 Blob

那么這時候我們要查看一下tree對象里面的內容,應該是a和b分別創建的git對象。

$ git cat-file -p 296e56023cdc034d2735fee8c0d85a659d1b07f4 100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 a 100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391    b

OK,我們到了這里才看到了,原來產生的兩個git對象都存放在了e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391這個文件里面,接下來我要講一下為什么會存放在一起。
首先來說一下sha-1的生成,一個將待存儲的數據外加一個頭部信息(header)一起做 SHA-1 校驗運算而得的校驗和,也就是說我們創建一個文件a他會根據文件a里面的數據和頭部信息拼在一起生成一個sha-1的值,這個頭部信息是固定的,如下圖所示:

正因為a和b文件的內容一樣都是blob空文本所以生成sha-1是一樣的所以會放在一個文件里面。

二、重寫歷史記錄

以下的命令幫我們重寫歷史記錄

  • git  commit  –amend可以產生一個新的提交用來替換掉當前所提交的這個commit
  • git  rebase  維護線性歷史
  • git  reset比如說我們做了一個合並的提交我們想要撤銷掉提交然歷史記錄跟沒有產生這樣的歷史記錄一個合並
  • git  reflog它維護了一個HEAD引用的歷史信息,通常配合git reset來使用
三、指令詳解

1. git commit –amend有時候我們提交完了才發現漏掉了幾個文件沒有添加,或者提交信息寫錯了。 此時,可以運行帶有–amend 選項的提交命令嘗試重新提交:
新建兩個文件a和b這時候我們只提交一個文件a到暫存區里面

touch  a b
$ git add a

提交a文件到暫存區內這時候我們就要來看一下生成的對象以及對象類型。

$ find .git/objects/ .git/objects/ .git/objects/e6 .git/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391 .git/objects/info .git/objects/pack

發現生成了一個e6的文件夾和9de29bb2d1d6434b8b29ae775ad8c2e48c5391文件名的文件。

$ git cat-file -t e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 blob

查看下新生成對象的類型是git對象blob。接下來我們將a文件進行提交到記錄里面去。

$ git commit -m 'commit file a' [master (root-commit) 96696f0] commit file a 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 a
提交完成后,我們再來看一下.git/objects文件下面的內容。
$ find .git/objects/ .git/objects/ .git/objects/49 .git/objects/49/6d6428b9cf92981dc9495211e6e1120fb6f2ba .git/objects/96 .git/objects/96/696f06370488cc9b271dbd870d8ba0d4e7ce3c .git/objects/e6 .git/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391 .git/objects/info .git/objects/pack

分別查看下96696f和496d64的對象類型。

$ git cat-file -t 496d6428b9cf92981dc9495211e6e1120fb6f2ba tree $ git cat-file -t 96696f06370488cc9b271dbd870d8ba0d4e7ce3c commit

這時候確定了96696f為commit對象,那么496d64就是tree對象。查看下他們的內容。

$ git cat-file -p 96696f06370488cc9b271dbd870d8ba0d4e7ce3c tree 496d6428b9cf92981dc9495211e6e1120fb6f2ba author BattleHeart <dwlsxj@126.com> 1452085041 +0800 committer BattleHeart <dwlsxj@126.com> 1452085041 +0800 commit file a

commit對象內容下面是指向496d64這個tree對象,並且有作者和提交時寫的提交信息。

$ git cat-file -p 496d6428b9cf92981dc9495211e6e1120fb6f2ba 100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391    a

Tree對象內容則是他的葉子內容,因為我們只提交了a文件所以只有a文件這個內容。
這時候我們發現應該a文件和b文件一起提交而不是分開提交,這時候就是用到接下來的命令–amend命令。那么首先要做的就先把b文件放到暫存區理面

$ git add b

這時候再使用git  commit  –amend就會將暫存區的文件一起提交

$ git commit –amend

當我們輸入的時候git commit –amend命令后我們先不着急操作,我們先來看一下產生的文件內容.git/objects文件里面。

$ find .git/objects/ -type f .git/objects/29/6e56023cdc034d2735fee8c0d85a659d1b07f4 .git/objects/49/6d6428b9cf92981dc9495211e6e1120fb6f2ba .git/objects/96/696f06370488cc9b271dbd870d8ba0d4e7ce3c .git/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391

會新生成一個296e56這樣一個對象。那么這個對象是什么呢?我們來仔細觀察下。

$ git cat-file -t 296e56023cdc034d2735fee8c0d85a659d1b07f4 tree $ git cat-file -p 296e56023cdc034d2735fee8c0d85a659d1b07f4 100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 a 100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391    b

啊哈,原來是個tree對象當然這里同樣也產生了blob對象只是內容和a內容一樣所以合並成一個文件了,因為296e56樹下面有兩個子節點。

我們這時候再進行填寫注釋,我們會發現我上一次提交的信息他給展現出來了,這里我們可以修改成first commit。當我們保存退出后就會產生如下輸出信息:

$ git commit --amend [master 433aedb] first commit Date: Wed Jan 6 20:57:21 2016 +0800
2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 a create mode 100644 b

這回我們再來看一下.git/objects里面內容。

$ find .git/objects/ .git/objects/ .git/objects/29 .git/objects/29/6e56023cdc034d2735fee8c0d85a659d1b07f4 .git/objects/43 .git/objects/43/3aedb274b59386535efa27c874d1ff5ded4a9b .git/objects/49 .git/objects/49/6d6428b9cf92981dc9495211e6e1120fb6f2ba .git/objects/96 .git/objects/96/696f06370488cc9b271dbd870d8ba0d4e7ce3c .git/objects/e6 .git/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391 .git/objects/info .git/objects/pack

多出來了一個433aed對象,那么這個對象是什么東西呢?以及里面存放的東西是什么?

$ git cat-file -t 433aedb274b59386535efa27c874d1ff5ded4a9b commit $ git cat-file -p 433aedb274b59386535efa27c874d1ff5ded4a9b tree 296e56023cdc034d2735fee8c0d85a659d1b07f4 author BattleHeart <dwlsxj@126.com> 1452085041 +0800 committer BattleHeart <dwlsxj@126.com> 1452085549 +0800 first commit

我們看到了這個是個commit對象並且這個commit對象指向了新生成的樹對象,以及commit對象沒有父節點也就是說本次提交相當於第一次提交。
2. git rebase
新建兩個文件a和b文件,添加文件a的內容為this is file a,添加文件內容b為this is file b。並將其添加到暫存區內。

git  add  .添加到暫存區內,查看一下.git/objects里面的內容,會產生兩個git對象,對象類型為blob。

$ git add . $ find .git/objects/ .git/objects/ .git/objects/77 .git/objects/77/486f708f7f7a0dab6f951148b75365081fcc3c .git/objects/ba .git/objects/ba/f5b90e3bc9f0c775c8d52764d353212259ffb3 .git/objects/info .git/objects/pack

查看下git對象的類型以及git對象下的內容。

$ git cat-file -t 77486f708f7f7a0dab6f951148b75365081fcc3c blob $ git cat-file -t baf5b90e3bc9f0c775c8d52764d353212259ffb3 blob $ git cat-file -p baf5b90e3bc9f0c775c8d52764d353212259ffb3 this is file a $ git cat-file -p 77486f708f7f7a0dab6f951148b75365081fcc3c this is file b

將兩個文件同時提交到記錄里面。Git commit

$ git commit -m 'first commit' [master (root-commit) 4bd85c6] first commit warning: LF will be replaced by CRLF in a. The file will have its original line endings in your working directory. warning: LF will be replaced by CRLF in b. The file will have its original line endings in your working directory. 2 files changed, 2 insertions(+) create mode 100644 a create mode 100644 b

這時候我們來看一下objects文件下內容。

$ find .git/objects/ .git/objects/ .git/objects/4b .git/objects/4b/d85c6b6ed389f268ca7c183e560fb9cb33430e .git/objects/77 .git/objects/77/486f708f7f7a0dab6f951148b75365081fcc3c .git/objects/ba .git/objects/ba/f5b90e3bc9f0c775c8d52764d353212259ffb3 .git/objects/bd .git/objects/bd/887f9b4dc0d570bd7cae7e604e0da5dd3f466a .git/objects/info .git/objects/pack

這時候會多兩個文件。

$ git cat-file -t bd887f Tree $ git cat-file -t 4bd85c Commit

這時候整體記錄的內容結構如下圖所示:

這時候我們新建一個分支test,並切換到test分支上面,在分支上面修改下a文件內容添加內容。

$ git branch test

這時候結構是這個樣子的。

這時候我們要在master分支上面修改下a文件並且提交a文件內容,這時候會產生新的commit對象以及tree對象和新生成git對象。
首先我們要做到在master分值上面先修改下a文件然后commit到記錄里面,然后在修改下b文件在提交到記錄里面。這是會產生兩條記錄讓我們來看一下:
首先做的就是添加a文件內容為this is master branch,並且添加到暫存區。

$ vim a

$ git add .

在來查看下新生成的git對象(blob),這個對象的sha-1為a4cb8b4(與上次對比)

$ find .git/objects/ .git/objects/ .git/objects/4b .git/objects/4b/d85c6b6ed389f268ca7c183e560fb9cb33430e .git/objects/77 .git/objects/77/486f708f7f7a0dab6f951148b75365081fcc3c .git/objects/a4 .git/objects/a4/cb8b4e0819ae97811f650886390e17e54bc93c .git/objects/ba .git/objects/ba/f5b90e3bc9f0c775c8d52764d353212259ffb3 .git/objects/bd .git/objects/bd/887f9b4dc0d570bd7cae7e604e0da5dd3f466a .git/objects/info .git/objects/pack

查看下a4cb8b4類型是blob那么查看下當前文件下的內容正是我們添加的內容一致。

$ git cat-file -t a4cb8b4 blob $ git cat-file -p a4cb8b4 this is file a this is master branch

這時候提交到版本控制里面去,將修改的文件a。

$ git commit -m 'master commit' [master warning: LF will be replaced by CRLF in a. The file will have its original line endings in your working directory. a6bcbb7] master commit warning: LF will be replaced by CRLF in a. The file will have its original line endings in your working directory. 1 file changed, 1 insertion(+)

當然提交上去的時候這時候必然會產生commit對象和新的tree對象。讓我們看一下誰是commit對象,誰是tree對象。

$ find .git/objects/ -type f .git/objects/4b/d85c6b6ed389f268ca7c183e560fb9cb33430e .git/objects/77/486f708f7f7a0dab6f951148b75365081fcc3c .git/objects/a4/cb8b4e0819ae97811f650886390e17e54bc93c .git/objects/ba/f5b90e3bc9f0c775c8d52764d353212259ffb3 .git/objects/bd/887f9b4dc0d570bd7cae7e604e0da5dd3f466a .git/objects/99/1f631d3674f37a8595fd782ce9fd57d354103a .git/objects/a6/bcbb77ada043da6df0a55918cac224bae50ef1

這時候我們發現比上一次對象多出了兩個對象分別是991f63和a6bcbb,多出了這兩個對象,那么我們來看一下這兩個對象就是是什么類型的。

$ git cat-file -t 991f63 tree $ git cat-file -p 991f63 100644 blob a4cb8b4e0819ae97811f650886390e17e54bc93c a 100644 blob 77486f708f7f7a0dab6f951148b75365081fcc3c    b

OK,991f63是tree對象,而且tree對象下面的內容中有一個是我們剛修改的a文件的git對象和第一次提交的b文件的git對象。
那么a6bcbb就是commit對象,且commit對象的指向是上面的tree對象。

$ git cat-file -t a6bcbb commit $ git cat-file -p a6bcbb tree 991f631d3674f37a8595fd782ce9fd57d354103a parent 4bd85c6b6ed389f268ca7c183e560fb9cb33430e author BattleHeart <dwlsxj@126.com> 1452165520 +0800 committer BattleHeart <dwlsxj@126.com> 1452165520 +0800 master commit

Ok,這時候整體的提交之后的示意圖如下所示:

這時候按照上面步驟看一下b的,這時候git對象變成了af379ed,commit對象變成了359118,tree對象變成了73f2e9。下面是操作記錄:

這時候生成commit結構圖:

接下來就要換做另一個分支了,按照上面的步驟從新來一遍,先修改下a文件,然后提交到暫存區,然后在修改下b文件再提交到暫存區,這里演示和分析過程就不做了。偷個懶,因為和上面分析過程是一樣的不在這里做重復說明了,這時候我們會看到一個分支,分支的commit提交記錄是這樣的。

詳細的結構圖我也畫了一份出來了,這樣方便我們對原文本的追蹤以及對整體原理的理解,通過分析哪些對象是新生成的那些對象是原有的就能分析出整體的結構走向。下面就上整體詳細的分析圖。

這時候我們會發現有兩個分支出來了,因為我們現在test分支上面,我們使用git rebase master命令來維護一個線性的記錄。
當我是用vim的時候肯定會產生沖突,這時候我們解決下沖突文件a和b文件再調用git  rebase  –continue命令即可,這里要詳細講解下合成的原理以及內容,當我git  rebase master的時候會產生兩個樹對象和一個git對象出來,這時候我們的HEAD頭部將會切換到master分支上面,也就是說接下來的操作是在master分支指向的最后一個分支上面進行與test分支上面每一個進行合並,這里我們來看一下git對象的內容是:這個樹對象的sha-1是:1de9f3f3cb3c87aaf2e43732ae13bf2aff1f8cda,我們會發現這個tree文件下面git對象是我們之前創建的,並不是這一次產生的,這個git對象是cce4bf,我們查看下他的內容如下所示:(這tree對象下面blob是test分支最后生成的內容)

$ git cat-file -p cce4bf this is file a this is test branch (file a)

而產生的第二個tree對象是3ada29384374b5219c3564ce1fa999ac4fde99aa是這個,我們發現他的git對象子節點也是之前創建的baf5b9,這個對象的內容是:(這個tree對象下面的blob是master分支第一次產生的內容)

$ git cat-file -p baf5b9 this is file a

這時候我們會發現還有一個git對象blob,這個git對象里面到底是什么東西呢?這會引起我們很大的想象,為什么會產生上面兩個tree並指向了之前的git對象呢?下面揭開謎底,原來新生成的blob對象就是a文件產生的沖突文件,文件的sha-1是:

.git/objects/72/038d17664a165e8a0bc65b9b028f3322e7f418 blob $ git cat-file -p 72038d this is file a <<<<<<< HEAD this is master branch ======= this is test branch (file a) >>>>>>> this is commit on test branch

這個文件是怎么產生的,這要說一下沖突產生的算法,我喜歡用數學的思維方式思考:給定兩個提交 A和 B,合並提交(commit)操作 A∨B 就可以描述為: [A∨B ]=[ A]+[ B]−[ C ] 這里的 C是A 和 B的合並共有項(最近提交樹祖先共同含有的部分),我們必須要“減去” C,因為如果不這樣的話,我們就會有兩個A∧B。也就是第一個樹對象是B也就是不是HEAD指針指向的master分支下的a的內容,HEAD指針下的內容就是A,內容如下:

this is file a this is master branch

A和B的共同之處就是this is file a這個內容(C),A+B-C就是我們現在看到沖突的文件blob(72038d)
這時候我們解決完沖突時候,git  add  . 之后就會生成一個新的blob對象就是這個對象。

.git/objects/07/cf0edd220a022c7447a8cf111bac3ed3422db9 blob對象 this is file a this is master branch this is test branch (file a)

這個對象我們在調用git  rebase  –continue的時候就會產生新的commit對象和tree對象。

.git/objects/77/14fb6ec6a19ba0d546d1cd10f89261445d3553 commit對象 $ git cat-file -p 7714fb tree 000fbac4af12a11f969d5e46d86d135f62adff8b parent 35911829097e90763845733d090e17c32d9a218c author BattleHeart <dwlsxj@126.com> 1452168533 +0800 committer BattleHeart <dwlsxj@126.com> 1452169355 +0800 this is commit on test branch .git/objects/00/0fbac4af12a11f969d5e46d86d135f62adff8b tree對象 Administrator@USER-20150201IC MINGW64 ~/Desktop/test (test) $ git cat-file -p 000fba 100644 blob 07cf0edd220a022c7447a8cf111bac3ed3422db9 a 100644 blob af379ed27a46022aca5a851bc55a8f69fd7afe3a    b

這里我們會發現b文件af379e是HEAD指針的指向的master分支的b文件的內容。

.git/objects/af/379ed27a46022aca5a851bc55a8f69fd7afe3a blob $ git cat-file -p af379e this is file b this is branch master(b)

這時候我們在進行第二次處理沖突內容,也就是b文件的內容,這時候產生的內容和上面的一樣的內容。這時候下面的內容就變成這樣:(第二部分的分析請讀者自行分析)一樣的步驟。
結構圖就變成如下的樣子:

整體的commit對象的圖就變成這樣:(c4’和c5’都是C3的基礎上進行合並的)

花絮

接下來的內容就是上圖中的節點,至於中間產生的節點只有第一次使用git  rebase  –continue的時候的結果,第二次的結果還請讀者自行分析。

.git/objects/43be0ce752cdabca0968e721fb030000093baa4d commit對象 tree eefcff716912aa684576546f63a06f3a52ce0959 parent 7714fb6ec6a19ba0d546d1cd10f89261445d3553 author BattleHeart <dwlsxj@126.com> 1452168857 +0800 committer BattleHeart <dwlsxj@126.com> 1452169398 +0800 下面tree對象 .git/objects/eefcff716912aa684576546f63a06f3a52ce0959 tree對象 $ git cat-file -p eefcff 100644 blob 07cf0edd220a022c7447a8cf111bac3ed3422db9 a 100644 blob 19f1ba2426ee5e72b95412c2707e75d9feb2833e b 下面得內容 .git/objects/07cf0edd220a022c7447a8cf111bac3ed3422db9 blob對象在上面 tree對象里 this is file a this is master branch this is test branch (file a) .git/objects/19/f1ba2426ee5e72b95412c2707e75d9feb2833e blob this is file b this is branch master(b) this is test branch (file b) 第一次commit對象 .git/objects/7714fb6ec6a19ba0d546d1cd10f89261445d3553 commit對象 $ git cat-file -p 7714fb tree 000fbac4af12a11f969d5e46d86d135f62adff8b parent 35911829097e90763845733d090e17c32d9a218c author BattleHeart <dwlsxj@126.com> 1452168533 +0800 committer BattleHeart <dwlsxj@126.com> 1452169355 +0800 this is commit on test branch .git/objects/00/0fbac4af12a11f969d5e46d86d135f62adff8b tree對象 Administrator@USER-20150201IC MINGW64 ~/Desktop/test (test) $ git cat-file -p 000fba 100644 blob 07cf0edd220a022c7447a8cf111bac3ed3422db9 a 100644 blob af379ed27a46022aca5a851bc55a8f69fd7afe3a b .git/objects/07/cf0edd220a022c7447a8cf111bac3ed3422db9 blob對象 this is file a this is master branch this is test branch (file a) .git/objects/af/379ed27a46022aca5a851bc55a8f69fd7afe3a blob $ git cat-file -p af379e this is file b this is branch master(b)

3.git reflog 羅列出所有的commit對象的sha-1碼和commit提交內容,看到我們上面merge操作我們想要換葯到之前的操作就用git reset –hard  HEAD@{5}這里的HEAD@{5}是根據reflog查出來的,hard就是將暫存區內容還原到之前。

四、結束語

本文的分析全是有本人自己分析,分析的時間有點長,結果有點有倉促,如果有哪里寫的不清楚或者不詳細的請指正,小丁再次謝過。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM