Git之深入解析如何重寫提交歷史

      網友投稿 788 2025-04-01

      一、Git 的提交原則

      了解了管理或者維護 Git 倉庫、實現代碼控制所需的大多數日常命令和工作流程,嘗試跟了蹤和提交文件的基本操作,并且掌握了暫存區和輕量級地分支及合并的威力。如果想進一步對 Git 深入學習,可以學習一些 Git 更加強大的功能,這些功能可能并不會在日常操作中使用,但在某些時候可能還是會起到一定的關鍵性作用。

      如果還不清楚 Git 的基礎使用流程、分支的管理、托管服務器的技術以及分布式工作流程等相關的技術和能力,請參考博客:

      Git之深入解析Git的安裝流程與初次運行Git前的環境配置;

      Git之深入解析本地倉庫的基本操作·倉庫的獲取更新和提交歷史的查看撤銷以及標簽別名的使用;

      Git之深入解析Git的殺手級特性·分支管理與變基的開發工作流以及遠程分支的跟蹤;

      Git之深入解析如何運行自己的Git倉庫托管服務器;

      Git之深入解析如何使用Git的分布式工作流程與如何管理多人開發貢獻的項目。

      許多時候,在使用 Git 時,可能想要修訂提交歷史,Git 允許我們在最后時刻做決定,可以在將暫存區內容提交前決定哪些文件進入提交,可以通過 git stash 來決定不與某些內容工作, 也可以重寫已經發生的提交就像它們以另一種方式發生的一樣。這可能涉及改變提交的順序,改變提交中的信息或修改文件,將提交壓縮或是拆分,或完全地移除提交,再將我們的工作成果與他人共享之前。

      Git 的基本原則之一是,由于克隆中有很多工作是本地的,因此可以在本地隨便重寫歷史記錄,然而一旦推送了工作,那就完全是另一回事了,除非有充分的理由進行更改,否則應該將推送的工作視為最終結果。簡而言之,在我們對它感到滿意并準備與他人分享之前,應當避免推送工作。

      二、修改最后一次提交

      修改最近一次提交可能是所有修改歷史提交的操作中最常見的一個,對于最近一次提交,我們往往想做兩件事情:簡單地修改提交信息, 或者通過添加、移除或修改文件來更改提交實際的內容。

      如果,只是想修改最近一次提交的提交信息,那么很簡單:

      $ git commit --amend

      1

      這條命令會將最后一次的提交信息載入到編輯器中供我們修改,當保存并關閉編輯器后,編輯器會將更新后的提交信息寫入新提交中,它會成為新的最后一次提交。

      另一方面,如果想要修改最后一次提交的實際內容,那么流程很相似:首先作出想要補上的修改,暫存它們,然后用 git commit --amend 以新的改進后的提交來替換掉舊有的最后一次提交。使用這個技巧的時候需要小心,因為修正會改變提交的 SHA-1 校驗和,它類似于一個小的變基,如果已經推送了最后一次提交就不要修正它。

      當在修補一次提交時,可以同時修改提交信息和提交內容,如果修補了提交的內容,那么幾乎肯定要更新提交消息以反映修改后的內容。另一方面,如果我們的修補是瑣碎的(如修改了一個筆誤或添加了一個忘記暫存的文件),那么之前的提交信息不必修改,只需作出更改,暫存它們,然后通過以下命令避免不必要的編輯器環節即可:

      $ git commit --amend --no-edit

      1

      三、修改多個提交信息

      為了修改在提交歷史中較遠的提交,必須使用更復雜的工具。Git 沒有一個改變歷史工具,但是可以使用變基工具來變基一系列提交,基于它們原來的 HEAD 而不是將其移動到另一個新的上面,通過交互式變基工具,可以在任何想要修改的提交后停止,然后修改信息、添加文件或做任何想做的事情;可以通過給 git rebase 增加 -i 選項來交互式地運行變基,必須指定想要重寫多久遠的歷史,這可以通過告訴命令將要變基到的提交來做到。

      例如,如果想要修改最近三次提交信息,或者那組提交中的任意一個提交信息,將想要修改的最近一次提交的父提交作為參數傳遞給 git rebase -i 命令,即 HEAD~2^ 或 HEAD~3。記住 ~3 可能比較容易,因為正嘗試修改最后三次提交;但是注意實際上指定了以前的四次提交,即想要修改提交的父提交:

      $ git rebase -i HEAD~3

      1

      再次記住這是一個變基命令,在 HEAD~3…HEAD 范圍內的每一個修改了提交信息的提交及其所有后裔都會被重寫。不要涉及任何已經推送到中央服務器的提交,這樣做會產生一次變更的兩個版本,因而使他人困惑。

      運行這個命令會在文本編輯器上給我們一個提交的列表,看起來像下面這樣:

      pick f7f3f6d changed my name a bit pick 310154e updated README formatting and added blame pick a5f4a0d added cat-file # Rebase 710f0f8..a5f4a0d onto 710f0f8 # # Commands: # p, pick = use commit # r, reword = use commit, but edit the commit message # e, edit = use commit, but stop for amending # s, squash = use commit, but meld into previous commit # f, fixup = like "squash", but discard this commit's log message # x, exec = run command (the rest of the line) using shell # b, break = stop here (continue rebase later with 'git rebase --continue') # d, drop = remove commit # l, label

      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

      需要重點注意的是相對于正常使用的 log 命令,這些提交顯示的順序是相反的。運行一次 log 命令,會看到類似這樣的東西:

      $ git log --pretty=format:"%h %s" HEAD~3..HEAD a5f4a0d added cat-file 310154e updated README formatting and added blame f7f3f6d changed my name a bit

      1

      2

      3

      4

      注意其中的反序顯示,交互式變基給你一個它將會運行的腳本,它將會在命令行中指定的提交(HEAD~3)開始,從上到下的依次重演每一個提交引入的修改。它將最舊的而不是最新的列在上面,因為那會是第一個將要重演的。

      我們需要修改腳本來讓它停留在想修改的變更上,要達到這個目的,只要將你想修改的每一次提交前面的 ‘pick’ 改為 ‘edit’。 例如,只想修改第三次提交信息,可以像下面這樣修改文件:

      edit f7f3f6d changed my name a bit pick 310154e updated README formatting and added blame pick a5f4a0d added cat-file

      1

      2

      3

      當保存并退出編輯器時,Git 將我們帶回到列表中的最后一次提交,送回命令行并提示以下信息:

      $ git rebase -i HEAD~3 Stopped at f7f3f6d... changed my name a bit You can amend the commit now, with git commit --amend Once you're satisfied with your changes, run git rebase --continue

      1

      2

      3

      Git之深入解析如何重寫提交歷史

      4

      5

      6

      7

      8

      9

      這些指令準確地告訴我們該做什么,輸入:

      $ git commit --amend

      1

      修改提交信息,然后退出編輯器,然后運行:

      $ git rebase --continue

      1

      這個命令將會自動地應用另外兩個提交,然后就完成。如果需要將不止一處的 pick 改為 edit,需要在每一個修改為 edit 的提交上重復這些步驟。每一次,Git 將會停止,讓我們修正提交,然后繼續直到完成。

      四、重新排序提交

      也可以使用交互式變基來重新排序或完全移除提交,如果想要移除 “added cat-file” 提交然后修改另外兩個提交引入的順序,可以將變基腳本從這樣:

      pick f7f3f6d changed my name a bit pick 310154e updated README formatting and added blame pick a5f4a0d added cat-file

      1

      2

      3

      改為這樣:

      pick 310154e updated README formatting and added blame pick f7f3f6d changed my name a bit

      1

      2

      當保存并退出編輯器時,Git 將我們的分支帶回這些提交的父提交,應用 310154e 然后應用 f7f3f6d,最后停止。事實修改了那些提交的順序并完全地移除了 “added cat-file” 提交。

      五、壓縮提交

      通過交互式變基工具,也可以將一連串提交壓縮成一個單獨的提交。在變基信息中腳本給出了有用的指令:

      # # Commands: # p, pick = use commit # r, reword = use commit, but edit the commit message # e, edit = use commit, but stop for amending # s, squash = use commit, but meld into previous commit # f, fixup = like "squash", but discard this commit's log message # x, exec = run command (the rest of the line) using shell # b, break = stop here (continue rebase later with 'git rebase --continue') # d, drop = remove commit # l, label

      1

      2

      3

      4

      5

      6

      7

      8

      9

      10

      11

      12

      13

      14

      15

      16

      17

      18

      19

      20

      21

      22

      23

      24

      如果,指定 “squash” 而不是 “pick” 或 “edit”,Git 將應用兩者的修改并合并提交信息在一起。所以,如果想要這三次提交變為一個提交,可以這樣修改腳本:

      pick f7f3f6d changed my name a bit squash 310154e updated README formatting and added blame squash a5f4a0d added cat-file

      1

      2

      3

      當保存并退出編輯器時,Git 應用所有的三次修改,然后將我們放到編輯器中來合并三次提交信息:

      # This is a combination of 3 commits. # The first commit's message is: changed my name a bit # This is the 2nd commit message: updated README formatting and added blame # This is the 3rd commit message: added cat-file

      1

      2

      3

      4

      5

      6

      7

      8

      9

      10

      11

      當保存之后就擁有了一個包含前三次提交的全部變更的提交。

      六、拆分提交

      拆分一個提交會撤消這個提交,然后多次地部分地暫存與提交直到完成所需次數的提交。例如,假設想要拆分三次提交的中間那次提交,想要將它拆分為兩次提交:第一個 “updated README formatting”,第二個 “added blame” 來代替原來的 “updated README formatting and added blame”。可以通過修改 rebase -i 的腳本來做到這點,將要拆分的提交的指令修改為 “edit”:

      pick f7f3f6d changed my name a bit edit 310154e updated README formatting and added blame pick a5f4a0d added cat-file

      1

      2

      3

      然后,當腳本帶我們進入到命令行時,重置那個提交,拿到被重置的修改,從中創建幾次提交。當保存并退出編輯器時,Git 帶我們到列表中第一個提交的父提交,應用第一個提交(f7f3f6d),應用第二個提交(310154e),然后進入命令行。 那里,可以通過 git reset HEAD^ 做一次針對那個提交的混合重置,實際上將會撤消那次提交并將修改的文件取消暫存。現在可以暫存并提交文件直到有幾個提交,然后當完成時運行 git rebase --continue:

      $ git reset HEAD^ $ git add README $ git commit -m 'updated README formatting' $ git add lib/simplegit.rb $ git commit -m 'added blame' $ git rebase --continue

      1

      2

      3

      4

      5

      6

      Git 在腳本中應用最后一次提交(a5f4a0d),歷史記錄看起來像這樣:

      $ git log -4 --pretty=format:"%h %s" 1c002dd added cat-file 9b29157 added blame 35cfb2b updated README formatting f3cc40e changed my name a bit

      1

      2

      3

      4

      5

      再次強調,這些改動了所有在列表中的提交的 SHA-1 校驗和,所以要確保列表中的提交還沒有推送到共享倉庫中。

      七、filter-branch

      有另一個歷史改寫的選項,如果想要通過腳本的方式改寫大量提交的話可以使用它。例如,全局修改我們的郵箱地址或從每一個提交中移除一個文件,這個命令是 filter-branch,它可以改寫歷史中大量的提交,除非項目還沒有公開并且其他人沒有基于要改寫的工作的提交做的工作,否則不應當使用它。

      有人粗心地通過 git add . 提交了一個巨大的二進制文件,我們想要從所有地方刪除,可能偶然地提交了一個包括一個密碼的文件,然而想要開源項目。filter-branch 是一個可能會用來擦洗整個提交歷史的工具,為了從整個提交歷史中移除一個叫做 passwords.txt 的文件,可以使用 --tree-filter 選項給 filter-branch:

      $ git filter-branch --tree-filter 'rm -f passwords.txt' HEAD Rewrite 6b9b3cf04e7c5686a9cb838c3f36a8cb6a0fc2bd (21/21) Ref 'refs/heads/master' was rewritten

      1

      2

      3

      –tree-filter 選項在檢出項目的每一個提交后運行指定的命令然后重新提交結果。例子中,從每一個快照中移除了一個叫作 passwords.txt 的文件,無論它是否存在。如果想要移除所有偶然提交的編輯器備份文件,可以運行類似 git filter-branch --tree-filter ‘rm -f *~’ HEAD 的命令。

      最后將可以看到 Git 重寫樹與提交然后移動分支指針,通常一個好的想法是在一個測試分支中做這件事,然后當你決定最終結果是真正想要的,可以硬重置 master 分支。為了讓 filter-branch 在所有分支上運行,可以給命令傳遞 --all 選項。

      假設已經從另一個源代碼控制系統中導入,并且有幾個沒意義的子目錄(trunk、tags 等等)。如果想要讓 trunk 子目錄作為每一個提交的新的項目根目錄,filter-branch 也可以那么做:

      $ git filter-branch --subdirectory-filter trunk HEAD Rewrite 856f0bf61e41a27326cdae8f09fe708d679f596f (12/12) Ref 'refs/heads/master' was rewritten

      1

      2

      3

      現在新項目根目錄是 trunk 子目錄了,Git 會自動移除所有不影響子目錄的提交。

      另一個常見的情形是在開始工作時忘記運行 git config 來設置名字與郵箱地址, 或者想要開源一個項目并且修改所有工作郵箱地址為個人郵箱地址。任何情形下,也可以通過 filter-branch 來一次性修改多個提交中的郵箱地址。 需要小心的是只修改我們自己的郵箱地址,所以使用 --commit-filter:

      $ 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

      1

      2

      3

      4

      5

      6

      7

      8

      9

      這會遍歷并重寫每一個提交來包含新郵箱地址,因為提交包含了它們父提交的 SHA-1 校驗和,這個命令會修改你的歷史中的每一個提交的 SHA-1 校驗和,而不僅僅只是那些匹配郵箱地址的提交。

      Git

      版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。

      版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。

      上一篇:WPS文字中怎么使用藝術字?
      下一篇:什么是倉庫管理系統(WMS)?
      相關文章
      亚洲人成网站日本片| 久久亚洲日韩精品一区二区三区| 亚洲神级电影国语版| 老汉色老汉首页a亚洲| 亚洲综合视频在线| 亚洲an天堂an在线观看| 亚洲男人天堂av| 亚洲av女电影网| 久久亚洲私人国产精品| 在线观看亚洲一区二区| 久久综合亚洲色一区二区三区| 亚洲美女视频网站| 亚洲成a人片77777群色| 久久久久精品国产亚洲AV无码| 激情亚洲一区国产精品| 亚洲一区二区三区高清不卡| 亚洲中文字幕久久久一区| 亚洲综合无码无在线观看| 亚洲av色香蕉一区二区三区 | 亚洲手机中文字幕| 亚洲成人高清在线观看| va天堂va亚洲va影视中文字幕| 亚洲一区二区三区国产精华液| 亚洲国产精品日韩av不卡在线| 精品久久亚洲一级α| 亚洲午夜AV无码专区在线播放 | 亚洲va中文字幕无码久久不卡| 亚洲Av熟妇高潮30p| 亚洲资源在线视频| 激情内射亚洲一区二区三区爱妻| 亚洲精品无码aⅴ中文字幕蜜桃| 国产精品亚洲а∨天堂2021| 亚洲中文字幕伊人久久无码| 亚洲精品成人网站在线观看| 日产亚洲一区二区三区| 亚洲中文字幕久久精品无码2021| 亚洲免费综合色在线视频| 午夜亚洲国产理论片二级港台二级| 亚洲国产成人久久一区久久| 亚洲免费观看视频| 91亚洲国产成人精品下载|