Git中rebase失敗了如何進行恢復


rebase失敗后的恢復

記一次翻車現場

記一次翻車的現場,很早之前提的PR后面由於需求的變便去忙別的事情了,等到要做這個需求的我時候,發現已經 落后版本了,並且有很多文件的沖突,然后就用rebase去拉代碼解決沖突,然后解完之后推代碼,但是之后發現一 個文件在解決沖突的時候丟失了。這時候去查看git提交歷史,發現rebase之后找不到這個文件了。

最后如何解決呢,要是把丟失的文件在寫一遍,那就真的變成了一名咸魚了。這時候我們就需要去弄明白rebase 的原理了。

rebase的使用流程

rebase就是把主分支的最新代碼拉取自己當前的開發分之上,只不過使用rebase,會形成 更加干凈的git線。

那么它的使用流程:

基於forked模式的開發。

1、forked代碼倉庫
2、 git clone <個⼈倉庫地址>
3、 添加遠程倉庫
 git remote add remote <遠程倉庫地址>
4、 查看遠程倉庫版本
git remote -v
5、 rebase
git pull remote master --rebase
6、 遇到沖突
git add .
git rebase --continue
git push -f origin XXXXX

rebase失敗如何恢復

使用reflog撤銷變基

$ git reflog
8c79a588 HEAD@{0}: checkout: moving from JM-5995 to JM-5997
e7b1f794 HEAD@{1}: checkout: moving from JM-5876 to JM-5995
97c945d8 HEAD@{2}: commit: fix response
81888803 HEAD@{3}: commit: sql的整理
427423c2 HEAD@{4}: checkout: moving from master to JM-5876
438a1d75 HEAD@{5}: rebase finished: returning to refs/heads/master
438a1d75 HEAD@{6}: pull remote master --rebase: checkout 438a1d75c4faf8056216980d6538de1fe11e0e4e
753e6308 HEAD@{7}: checkout: moving from JM-5876 to master
427423c2 HEAD@{8}: rebase finished: returning to refs/heads/JM-5876
427423c2 HEAD@{9}: rebase: 外審整改計划修改文檔的命名
87627087 HEAD@{10}: rebase: 外審整改計划導入的細節部分修改,增加條款信息的查詢
c8b18fc1 HEAD@{11}: pull remote master --rebase: 為甚整改計划修改文檔的命名
9e1450c4 HEAD@{12}: pull remote master --rebase: 外審整改計划增加條款項的查詢
c25f7ab4 HEAD@{13}: pull remote master --rebase: 修改參數的大小寫問題
b16eb983 HEAD@{14}: pull remote master --rebase: 外審整改計划添加詳情添加外審計划的信息
c5519548 HEAD@{15}: pull remote master --rebase: 外審整改計划添加是否關聯不符合項的查詢條件
29819e65 HEAD@{16}: pull remote master --rebase: 外審整改計划修改修改測試文件
4e3a6084 HEAD@{17}: pull remote master --rebase: 外審整改計划修改添加文件上傳的功能
2e516dfa HEAD@{18}: pull remote master --rebase: 外審整改計划修改count的查詢方式
8e970d7c HEAD@{19}: pull remote master --rebase: 外審整改計划初步提交
1e0fa8b3 HEAD@{20}: pull remote master --rebase: sql
9c384393 HEAD@{21}: pull remote master --rebase: checkout 9c384393b1913f67c4eef633ecdeaa23cc4ce397
8b9129ed HEAD@{22}: checkout: moving from JM-5997 to JM-5876
8c79a588 HEAD@{23}: commit: 修復外審計划錯誤的查詢
9a08a751 HEAD@{24}: commit: fix test response
43969771 HEAD@{25}: commit: code review修改導出的狀態碼
6b5e693f HEAD@{26}: commit: code review調整錯誤的返回的位置
010c7c8e HEAD@{27}: commit: code review代碼細節修改
deca577b HEAD@{28}: commit: code review代碼細節修改
874fed06 HEAD@{29}: commit: 修改導入的模板的路徑
9607489f HEAD@{30}: rebase finished: returning to refs/heads/JM-5997
9607489f HEAD@{31}: rebase: 包資源的替換
a4f5fb85 HEAD@{32}: rebase: 包資源的替換

 

上面是所有的日志 我們可以逐條分析,找到我們rebase的節點 只要輸入下面的命令就好了

git reset --hard HEAD@{3}

或者

git reset --hard 81888803

rebase使用的意義

使用rebase的提交歷史

對比merge

 

使用rebase會得到一個干凈的,線性的提交歷史,沒有不必要的合並。 使用merge能夠保存項目完整的歷史,並且避免公共分之上的commit。

rebase工作的原理

為了弄清楚rebase的原理,首先需要弄清楚git的工作原理。

git工作原理

首先我們先來了解下git的模型。 首先我們可以看到在每個項目的下面都有一個.git的隱藏目錄

 

關於git的一切都存儲在這個目錄里面(全局配置除外)。這里面有一些子目錄和文件, 記錄到了git所有的信息。

 

 

 

文件里面存儲的都是一些配置文件:

  • info:初始化時只有這個文件,用於排除提交規則,與 .gitignore 功能類似。他們的區別在 於.gitignore 這個文件本身會提交到版本庫中去,用來保存的是公共需要排除的文件;而info/exclude 這里設置的則是你自己本地需要排除的文件,他不會影響到其他人,也不會提交到版本庫中去。

  • hooks:這個目錄很容易理解, 主要用來放一些 git 鈎子,在指定任務觸發前后做一些自定義的配置,這 是另外一個單獨的話題,本文不會具體介紹。

  • objects:用於存放所有 git 中的對象,里面存儲所有的數據內容,下面單獨介紹。

  • logs:用於記錄各個分支的移動情況,下面單獨介紹。

  • refs:用於記錄所有的引用,下面單獨介紹。

  • HEAD:文件指示目前被檢出的分支

  • index:文件保存暫存區信息

git對象

git是面向對象的!

舉個栗子

假如我們init了一個新的倉庫,然后提交了兩個文件,那么會有那些對象呢?

$ git init
$ echo 'aaaaa'>a.txt
$ echo 'bbbbb'>b.txt
$ git add *.txt

上面提交了兩個文件到了暫存區,我們了解到對象都存儲在object文件夾中,那我們去到里面看下。

$ tree .git/objects
.git/objects
├── cc
│   └── c3e7b48da0932cc0f7c4ce7b4fd834c7032fe1
├── db
│   └── 754dbd326f1b7c530672afbbfef8d9223033b7
├── info
└── pack

git cat-file [-t] [-p] 號稱git里面的瑞士軍刀,我們來剖析下,t可以查看object的類型,-p可 以查看object儲存的具體內容。

$ git cat-file -t ccc3e
blob
$ git cat-file -p ccc3e
aaaaa

可以發現object是一個blob類型的節點,內容是aaa,這就是object存儲的內容

這里我們遇到了第一種Git object,blob類型,它只儲存的是一個文件的內容,不 包括文件名等其他信息。然后將這些信息經過SHA1哈希算法得到對應的哈希值 ccc3e7b48da0932cc0f7c4ce7b4fd834c7032fe1 ,作為object在git中的唯一身份證。

然后我們進行一次commit

 git commit -am '[+] init'
[master(根提交) d290482] [+] init
 2 files changed, 2 insertions(+)
 create mode 100644 a.txt
 create mode 100644 b.txt

再次查看object

$ tree .git/objects
.git/objects
├── 3b
│   └── e060dffd163e01ca6f44dfc6746dda5ae19d5d
├── cc
│   └── c3e7b48da0932cc0f7c4ce7b4fd834c7032fe1
├── d2
│   └── 904827c81ba3c05072d66d1dd466e01bcdee69
├── db
│   └── 754dbd326f1b7c530672afbbfef8d9223033b7
├── info
└── pack
里面變成了四個對象了

讓我們‘瑞士軍刀’剖析下

$ git cat-file -t 3be0
tree

git cat-file -p 3be0
100644 blob ccc3e7b48da0932cc0f7c4ce7b4fd834c7032fe1    a.txt
100644 blob db754dbd326f1b7c530672afbbfef8d9223033b7    b.txt

我們發現這里出現了另一種object類型-tree。它將當前的提交目錄打了一個快照,存儲了當前提交的目錄結構。

再看下兩一個object里面的信息

$ git cat-file -p d2904
tree 3be060dffd163e01ca6f44dfc6746dda5ae19d5d
author liz <rick.lizhan@gmail.com> 1576828975 +0800
committer liz <rick.lizhan@gmail.com> 1576828975 +0800

里面存儲的是提交者的信息

那么有一個問題,一次commit生成的快照是全局的還是提交部分的呢? 我們再來看。 修改a.txt,然后提交,看看新的提交

修改a.txt文件里面的內容為
aaaaa
aaaaa

提交commit

$ git commit -am '[+] init'
[master f5af32c] [+] init
 1 file changed, 1 insertion(+)

我們再次打印樹結構

$ tree .git/objects
.git/objects
├── 1c
│   └── 30d6ba3439ec5e8e68aed15a79371db2f9be8d
├── 3b
│   └── e060dffd163e01ca6f44dfc6746dda5ae19d5d
├── 51
│   └── 200a14c72d35c97979bea3b46bee414e86185b
├── cc
│   └── c3e7b48da0932cc0f7c4ce7b4fd834c7032fe1
├── da
│   └── 8ebda5480840ecde47656e10317c55b965ad3b
├── db
│   └── 754dbd326f1b7c530672afbbfef8d9223033b7
├── f5
│   └── af32c14e49bb408de53e4ed22f1b8dd95c2353
├── info
└── pack

對象數量變成了8個也就是之前的2倍,所以可以看出存儲的是當前全新的文件快照,而不 是僅僅提交部分的快照。 那么問題來了,這樣有什么好處呢,每次會新建一個全新的blob object 其實是Git在時間很空間上的一個取舍,如果我們需要checkout或commit。或對比兩個commit 之間的差異。如果存儲的只是變更的部分,如果需要拿到一個commit的內容都需要從第一個 提交開始查找,會浪費很多的時間,而存儲權限快照的方法就很快了,這種策略相當於通過 空間換時間。

總結下: 當我們新增兩個文件的時候,就會有兩個數據對象,每個文件都對應一個數據對象。當文件被 修改時,即使新增了一個字母,也會生成一個數據對象。

其次會有一個樹對象來維護一些列的數據對象,叫樹對象的原因是它持有的不僅可以是數據 對象,還可以是兩一個對象。比如某次提交了兩個文件和一個文件夾,那么樹對象里面就有三 個對象,兩個是數據對象,文件夾則用另一個樹對象表示。這樣遞歸下去就可以表示任意層 次的文件了。

最后則是提交對象,每個提交對象都有一個樹對象,用來表示某一次提交所涉及的文件。除此 以外,每一個提交還有自己的父提交,指向上一次提交的對象。

 

 

git引用

git是面向對象的,里面的標簽和分支都是指向提交對象的指針。

git日志

在 .git/logs 目錄中,有一個文件夾和一個 HEAD 文件,每當 HEAD 引用改變了指向的位置,就會在 .git/logs/HEAD 中 添加了一個記錄。而 .git/logs/refs/heads 這個目錄中則有多個文件,每個文件對應一個分支,記錄了這個分支 的指向位置 發生改變的情況。 當我們執行 git reflog 的時候,其實就是讀取了 .git/logs/HEAD 這個文件。

rebase的原理

對於rebase的撤銷,其實並不是真正意義上的撤銷,只是通過reset修改HEAD和當前分支的指向,后面的提交只是沒有指針的指向。 但是還是真實存在的。

參考


免責聲明!

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



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