https://blog.yorkxin.org/posts/2011/07/29/git-rebase/

最近剛好有個機會整理很亂的 Git commit tree,終於搞懂了 rebase 的用法,筆記一下。

大家都知道 Git 有個特色就是 branch 開很大開不用錢,但很多 branches 各自開發,總要在適當時機 merge 進去 master 。看過很多 git 操作指南都告訴我們,可以妥善利用 rebase 來整理看似很亂或是中途可能不小心手滑 commit 錯的 commits ,甚至可以讓 merge 產生的線看起來比較簡單,不會有跨好幾十個 commits 的線。

rebase 的意義:重新定義參考基準

首先要提一下 rebase 的意思,我擅自的直譯是「重新 (re-)定義某個 branch 的參考基準 (base)」。把這個意思先記起來,比較容易理解 rebase 的運作原理。就好比移花接木那樣(稼接),把某個樹枝接到別的樹枝。

在 git 中,每一個 commit 都可以長出 branch ,而 branch 的 base 就是它生長出來的 commit ,rebase 也就是把該 branch 所長出來的 commit 給改去另一個 commit 。不過,因為 rebase 會調整 commit 的先後關係,弄不好的話可能會把你正在操作的 branch 給搞爛,所以在做 rebase 之前,最好開一個 backup branch ,什麼時候出差錯的話,reset 回 backup 就行了。

以下用實際的例子來操作比較容易解釋。看 log 的程式是 GitX (L) 。

Update 2012/06/28:也可以看 ihower 的錄影示範 ,實際操作會比讀文字來得容易懂。

例如我要寫個網頁,列出課堂上的學生。我把樣式的設計 (style) 跟主幹 (master) 分開,檔案有 index.htmlstyle.css

到目前為止有以下的 commit history:

style 完成了一小部份,而接下來要修飾的頁面是 master 裡面有改過的,如何讓 style 可以繼承 master 呢?就是用 rebase 把 style branch 給接到 master 後面了,因為 rebase 是「重新定義基準點」。就像是在稼接時,把新枝的根給「接」在末梢上。

rebase 的基本指令是 git rebase <new base-commit> ,意思是說,把目前 checkout 出來的 branch 分支處改到新的 commit。而 commit 可以使用 branch 去指(被指中的 commit 就是該 branch 的 HEAD),所以現在要把 style 這個 branch 接到 masterHEADdc39a81e),就是在 style 這個 branch 執行

git rebase master

完成之後,圖變這樣:

果然順利接起來了。

而在執行的過程中會看到:

First, rewinding head to replay your work on top of it...
Applying: set body's font to helvetica
Applying: adjust page width and alignment

這是它的操作方式,照字面上的意思,就是它會嘗試把當前 branch 的 HEAD 給指到你指定的 commit (在這裡是原本 masterHEAD,也就是 dc39a81e),然後把每個原本在 style 上面的 commits (d242d00c..0b373e34) 給重新 commit 進去 style 這個 branch (re-apply commits)。也由於是「重新 commit」,所以 rebase 以後的 commit ID (SHA) 都不一樣。

那如果過程中有 conflict 呢?後文會提到。

fast-forwarding: 可以的話,直接改指標,不重新 commit

接著再開個新的 branch 叫 list ,專門改學生清單,同時另一個人也在改 style 這個 branch ,修飾網頁的整體裝飾。改啊改,變成這樣分叉的兩條線:

list 改到一個段落,沒有問題了,就想 merge 進 master 。在 master branch 做

git merge list

這時 git 發現,剛好 master 直接指到 listHEAD commit 也行 ,所以 git 直接就改了 master 的 commit ID ,也就是所謂的 fast-forward,熟悉 C 語言的同學應該對這種指標移動不陌生。完成之後就是這樣:

rebase --onto: 指定要從哪裡開始接枝

list 繼續改,style 還是繼續改,變這樣:

現在 style 要開始裝飾學生清單了,而學生清單是 list 這個 branch 在改的。於是 style 應該要 rebase 到 list ,可是這時管 list 的說,我後面幾個 commits 還沒敲定,你先拿 64a00b7e (add their ages) 這個 commit 當基準,這我改好了。所以這時候,應該要把 style 這個 branch 接到 64a00b7e 的後面。

該怎麼辦呢?這時就要用 git rebase --onto 了。指令是

git rebase --onto <new base-commit> <current base-commit>

意思是說,把當前 checkout 出來的 branch 從 <current base-commit> 移到 <new base-commit> 上面 ,就像是在稼接時,把新枝的根給「種」在某個點上,而不是接在末梢。(這似乎也是稼接最常用的方式?有請懂園藝同學的指教一下)

再看一下 commit history:

現在 style 是 based on dc39a81e (add some students),要改成 based on 64a00b7e (add their ages),也就是

  • <current base-commit> = dc39a81e
  • <new base-commit> = 64a00b7e

那就來試試看

git rebase --onto 64a00b7e dc39a81e

果然達到了目的,style 現在是 based on 64a00b7e 了(當然 commit IDs 也都不同了)。

conflict 的處理

接著改 style 的人修改了學生清單的樣式,可是他很機車,他要改 index.html 裡面的東西(實際情況是,list 裡寫了一個 table,但寫 css 總要有些 classid 的 attributes 才能設定)。剛好改 list 的人也在他自己的 branch 裡面改,這時候,在 rebase 試著 re-apply commits 的過程中,必定會產生 conflict。

現在 list 要利用到 style 裡面修飾好的樣式,在這個情況下,就是把 list 給 rebase 到 style 上面,也就是在 list branch 做 git rebase style 。不過你會看到這個:

First, rewinding head to replay your work on top of it...
Applying: add gender column
Using index info to reconstruct a base tree...
Falling back to patching base and 3-way merge...
Auto-merging index.html
CONFLICT (content): Merge conflict in index.html
Failed to merge in the changes.
Patch failed at 0001 add gender columnWhen you have resolved this problem run "git rebase --continue".
If you would prefer to skip this patch, instead run "git rebase --skip".
To restore the original branch and stop rebasing run "git rebase --abort".

跟預期的一樣出現了 conflict。當然,它會先試著自動 merge ,但如果改到的行有衝突,那就得要手動 merge 了,打開他說有衝突的檔案,改成正確的內容,接著使用 git add <file> (要把該檔案加進去 staging area,處理 rebase 的程式才能 commit),再 git rebase --continue

完成以後就會像這樣:

Interactive Mode: 偷天換日,自定重新 commit 的詳細步驟

接著 stylelist 又陸續改了一些東西,主要是 list 裡面加了表單元件,而 style 則繼續修飾網頁整體設計。到了一個段落,該輪到 style 修飾 list 的表單了。目前的 commit history 長這樣:

不過在 style 要 rebase 到 list 上面之前,管 list 的人想把 list 上面的一些 commits 給整理過,因為他發現有這些問題:

  • "wrap the form with div" 太後面了,想移到前面
  • "fix typo of age field name""add student id and age..." 可以合併
  • "add student id and age ..." 裡面東西太多,該拆成兩個
  • "form to add more *studetns*" 這 message 有錯字 "studetns"
  • "add gender select box" 裡面的程式碼有打錯字(囧

上面提到了 rebase 運作的方式是重新 commit 過一遍,那這個「重新 commit」的過程,能不能讓程式設計師來干預,達到偷天換日修改 commit 的目的呢?當然可以,只要利用 rebase 的 Interactive Mode。Git 的靈活就在這裡,連 commit 的內容都可以改。

如何啟動 interactive mode 呢?只要加入 -i 的參數就行了。以這個例子來說,list branch 是 based on 0580eab8 (fill in gender column) ,要從這個 commit 後面重新 apply 一次 commits ,也就是:

git rebase -i 0580eab8

接著會以你的預設編輯器打開一個檔案叫做 .git/rebase-merge/git-rebase-todo ,裡面已經有一些 git 幫你預設好的內容了,其實就是原本 commits 的清單,你可以修改它,告訴 git 你想怎麼改:

git rebase -i

pick 2c97b26 form to add more studetns
pick fd19f8e add student id and age field into the form
pick 02849bf fix typo of age field name
pick bd73d4d wrap the form with div
pick 74d8a3d add gender select box# Rebase 0580eab..74d8a3d onto 0580eab
# ...[chunked]

第一個欄位就是操作指令,指令的解釋在該檔案下方有:

  • pick = 要這條 commit ,什麼都不改
  • reword = 要這條 commit ,但要改 commit message
  • edit = 要這條 commit,但要改 commit 的內容
  • squash = 要這條 commit,但要跟前面那條合併,並保留這條的 messages
  • fixup = squash + 只使用前面那條 commit 的 message ,捨棄這條 message
  • exec = 執行一條指令(但我沒用過)

此外還可以調整 commits 的順序,直接剪剪貼貼,改行的順序就行了。

調整 commit 順序、修改 commit message

首先我想要把 "wrap the form with div" 移到 "form to add more studetns" 後面,然後 "form to add more studetns" 要改 commit message (有 typo),那就改成這樣:

git rebase -i

reword 2c97b26 form to add more studetns
pick bd73d4d wrap the form with div
pick fd19f8e add student id and age field into the form
pick 02849bf fix typo of age field name
pick 74d8a3d add gender select box

接著儲存檔案後把檔案關掉(如 vim 的 :wq),就開始執行 rebase 啦,遇到 reword  時會再跳出編輯器,讓你重新輸入 commit message 。這時我把 studetns 改正為 students ,然後就跟平常 commit 一樣,存檔並關掉檔案。

git commit

form to add more students# Please enter the commit message for your changes. Lines starting
# ...[chunked]

完成後會看到:

Successfully rebased and updated refs/heads/list.

再看 commit history ,的確達到了目的,而且 list 這個 branch 一樣還是 based on 0580eab8 ,後面那些剛剛 rebase 過的 commits 統統換了 commit ID :

合併 commits

剩下這些要做:

  • "fix typo of age field name""add student id and age..." 可以合併
  • "add student id and age ..." 裡面東西太多,該拆成兩個
  • "add gender select box" 裡面的程式碼有打錯字

現在來試試看合併,一樣是 git rebase -i 0580eab8 ,並使用 fixup 來把 commit 給合併到上一個(如果用 squash 的話,會讓你修改 commit message ,修改時會把多個要連續合併的 commit messages 放在同一個編輯器裡):

git rebase -i

pick c3cff8a form to add more students
pick 7e128b4 wrap the form with div
pick 0d450ea add student id and age field into the form
fixup 8f5899e fix typo of age field name
pick e323dbc add gender select bo

完成後再看 commit history ,的確合併了:

修改、拆散 commit 內

剩下了拆 commit 和訂正 commit 內容。現在先來做訂正 commit ,這個學會了就知道怎麼拆 commit 了。

在這裡下 edit 指令來編輯 commit 內容:

git rebase -i

pick c3cff8a form to add more students
pick 7e128b4 wrap the form with div
pick 53616de add student id and age field into the form
edit c5b9ad8 add gender select box

存檔並關閉之後,現在的狀態是停在剛 commit 完 "add gender select box" 的時候,所以現在可以偷改你要改的東西,存檔以後把改的檔案用 git add 加進 staging area ,再打

git rebase --continue

來繼續,這時候因為 staging area 裡面有東西,git 會將它們與 "add gender select box" 透過 commit --amend 一起重新 commit 。

最後是拆 commit 。怎麼拆呢?剛剛做了 edit ,不是停在該 commit 之後嗎?這時候就可以偷偷 reset 到 HEAD^ (即目前 HEAD 的前一個),等於是退回到 HEAD 指到的 commit 的前一個,於是該 commit 的 changes 就被倒出來了,變成 changed but not staged for commit ,再根據你的需求,把 changes 給一個一個 commit 就行了。

實際的操作如下。首先是用 edit 指令來編輯 commit 內容:

git rebase -i

pick c3cff8a form to add more students
pick 7e128b4 wrap the form with div
edit 53616de add student id and age field into the form
pick 4dbcf49 add gender select box

接著使用

git reset HEAD^

來把目前的 HEAD 指標給指到 HEAD 的前一個,指完之後,原本 HEAD commit 的內容就被倒出來,並且也不存在 stage area 裡面, git 會提示有哪些檔案現在處於 changed but not staged for commit:

Unstaged changes after reset:
M   index.html

現在我可以一個一個 commit 了,原本是 add student id and age field ,我想拆成一次加 student id field ,一次加 age field 。commit 完成以後,再打

git rebase --continue

這次因為 staging area 裡面沒東西,所以就繼續 re-apply 剩下的 commits 。

現在打開 log 看,拆成兩個啦!

掌管 list branch 的人折騰完了,便告訴管 style 的說,可以 rebase 了,git 再度拯救了苦難程序員的一天


更多 rebase :

  • Git 版本控制系統(3) 還沒 push 前可以做的事 by ihower
  • 寫給大家的 Git 教學 by littlebtc

Git-rebase 小筆記相关推荐

  1. C# 一些學習小筆記及技巧

    學習及工作中積累了一些零零碎碎的小筆記及技巧(C#),重溫和匯總一下. 1. 類別轉換時,如果你未能確定數值有效,使用TryParse 比 Parse 要更安全, TryParse 不會引發異常. 2 ...

  2. Git 初學筆記 - 指令操作教學

    Git 是分散式的版本控制系統, 從架設.簡易操作.設定, 此篇主要是整理基本操作.遠端操作等. 註: Git 的範圍太廣了, 把這篇當作是初學入門就好了. 注意事項 由 project/.git/c ...

  3. git rebase/reset小计

    http://www.cnblogs.com/kym/archive/2010/08/12/1797937.html git rebase,顾名思义,就是重新定义(re)起点(base)的作用,即重新 ...

  4. 你什么时候使用git rebase而不是git merge?

    什么时候建议使用git rebase与git merge ? 成功改造后我还需要合并吗? #1楼 在合并/ rebase之前: A <- B <- C [master] ^\D <- ...

  5. 这一次彻底搞懂 Git Rebase

    使用 Git 已经好几年了,却始终只是熟悉一些常用的操作.对于 Git Rebase 却很少用到,直到这一次,不得不用. 一.起因 上线构建的过程中扫了一眼代码变更,突然发现, commit 提交竟然 ...

  6. (筆記) 如何在字串中從指定字元抓到指定字元(pointer版)?

    Abstract 這是一個實務上常用的功能,可惜在C語言的string.h並沒有提供,本文實作出一個小function達到此功能,並搭配pointer. Introduction 在(筆記) 如何在字 ...

  7. 自动化部署之git merge和git rebase的区别

    命令行测试 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 3 ...

  8. (筆記) 如何在字串中從指定字元抓到指定字元(pointer版)? (C/C++) (C)

    Abstract 這是一個實務上常用的功能,可惜在C語言的string.h並沒有提供,本文實作出一個小function達到此功能,並搭配pointer. Introduction 在(筆記) 如何在字 ...

  9. git rebase使用简介

    一.起因 上线构建的过程中扫了一眼代码变更,突然发现, commit 提交竟然多达 62 次.我们来看看都提交了什么东西: 这里我们先不说 git 提交规范,就单纯这么多次无用的 commit 就很让 ...

最新文章

  1. 2020年必学的 10 大算法
  2. JQuery:JQuery捕获HTML
  3. 【C++ 语言】面向对象 ( 继承 | 重写 | 子类调用父类方法 | 静态多态 | 动态多态 | 虚函数 | 纯虚函数 )
  4. JavaScript实现hammingDistance汉明距离算法(附完整源码)
  5. can协议解析字符串的原理
  6. 概述类的加载器及类加载过程
  7. N Queen(代码、分析、汇编)
  8. Linux 文件系统与设备文件系统 (二)—— sysfs 文件系统与Linux设备模型
  9. python如何循环使用input_python基础知识input到while循环
  10. MySQL入门 (一) : 资料库概论与MySQL的安装
  11. GitHub提速方法大揭秘,10M速度使用无忧
  12. final 和static的关系
  13. 2018-2019-2 20165235《网络对抗技术》Exp7 网络欺诈防范
  14. JavaScript按概率随机生成事件
  15. 微信小程序多位验证码/密码输入框
  16. Shiro与Spring整合
  17. 设计模式--建造者模式(C++实现)
  18. [201011][Maven 实战][许晓斌][著]
  19. bem css_CSS体系结构:块元素修饰符(BEM)和原子CSS
  20. Centos jenkins 插件安装失败

热门文章

  1. [CentOS Python系列] 三.阿里云MySQL数据库开启配置及SQL语句基础知识
  2. 【python数据挖掘课程】二十一.朴素贝叶斯分类器详解及中文文本舆情分析
  3. iOS之深入解析静态库和动态库
  4. 130. Surrounded Regions 被围绕的区域
  5. python网络编程——IO多路复用之epoll
  6. Java中集合(一)Collection 、ListE 、ArrayListE
  7. 【机器视觉】 throw算子
  8. 【Linux】一步一步学Linux——ss命令(170)
  9. kuka机器人if逻辑编程_【视频】说说工业机器人控制与PLC通讯
  10. 为什么计算机的编码那么多,为什么中国剩余定理可用于计算机编码?