-
A1. 附錄 A: Git 在其他環境
- A1.1 圖形介面
- A1.2 Visual Studio 中的 Git
- A1.3 Visual Studio Code 中的 Git
- A1.4 IntelliJ / PyCharm / WebStorm / PhpStorm / RubyMine 中的 Git
- A1.5 Sublime Text 中的 Git
- A1.6 Bash 中的 Git
- A1.7 Zsh 中的 Git
- A1.8 PowerShell 中的 Git
- A1.9 小結
-
A2. 附錄 B: 在應用程式中嵌入 Git
-
A3. 附錄 C: Git 命令
7.13 Git 工具 - 替換
替換
正如我們之前強調的,Git 物件資料庫中的物件是不可更改的,但 Git 提供了一種有趣的方式,可以假裝將其資料庫中的物件替換為其他物件。
replace
命令允許你指定 Git 中的一個物件,並告訴 Git“每當你引用這個物件時,都假裝它是另一個物件”。這最常用於在不透過 git filter-branch
等命令重構整個歷史記錄的情況下,用另一個提交替換歷史記錄中的一個提交。
例如,假設你有一個龐大的程式碼歷史記錄,並希望將你的倉庫拆分為一個供新開發人員使用的短歷史記錄,以及一個供對資料探勘感興趣的人使用的更長、更大的歷史記錄。你可以透過“替換”新歷史線中的最早提交,使其成為舊歷史線中的最新提交,從而將一個歷史記錄嫁接到另一個歷史記錄上。這很好,因為它意味著你實際上不必重寫新歷史記錄中的每個提交,而通常為了將它們連線在一起是需要這樣做的(因為父子關係會影響 SHA-1 值)。
我們來試試看。讓我們取一個現有倉庫,將其分成兩個倉庫,一個最近的和一個歷史的,然後我們將看看如何透過 replace
命令在不修改最近倉庫的 SHA-1 值的情況下重新組合它們。
我們將使用一個包含五個簡單提交的簡單倉庫
$ git log --oneline
ef989d8 Fifth commit
c6e1e95 Fourth commit
9c68fdc Third commit
945704c Second commit
c1822cf First commit
我們想將其分解為兩條歷史線。一條歷史線從提交一到提交四——這將是歷史線。第二條歷史線將只包含提交四和五——這將是最近的歷史記錄。

好的,建立歷史記錄很容易,我們只需在歷史記錄中放置一個分支,然後將該分支推送到新遠端倉庫的 master
分支。
$ git branch history c6e1e95
$ git log --oneline --decorate
ef989d8 (HEAD, master) Fifth commit
c6e1e95 (history) Fourth commit
9c68fdc Third commit
945704c Second commit
c1822cf First commit

history
分支現在我們可以將新的 history
分支推送到我們新倉庫的 master
分支
$ git remote add project-history https://github.com/schacon/project-history
$ git push project-history history:master
Counting objects: 12, done.
Delta compression using up to 2 threads.
Compressing objects: 100% (4/4), done.
Writing objects: 100% (12/12), 907 bytes, done.
Total 12 (delta 0), reused 0 (delta 0)
Unpacking objects: 100% (12/12), done.
To git@github.com:schacon/project-history.git
* [new branch] history -> master
好的,我們的歷史記錄已釋出。現在更難的部分是截斷我們的近期歷史記錄,使其更小。我們需要一個重疊部分,這樣我們就可以用另一個倉庫中的等效提交替換其中一個倉庫中的提交,所以我們將把它截斷到只剩下提交四和五(這樣提交四就重疊了)。
$ git log --oneline --decorate
ef989d8 (HEAD, master) Fifth commit
c6e1e95 (history) Fourth commit
9c68fdc Third commit
945704c Second commit
c1822cf First commit
在這種情況下,建立一個包含如何擴充套件歷史記錄說明的基礎提交會很有用,這樣其他開發人員就知道如果他們遇到截斷歷史記錄中的第一個提交併且需要更多資訊時該怎麼做。所以,我們要做的就是建立一個帶有說明的初始提交物件作為我們的基點,然後將剩餘的提交(四和五)在其之上進行變基。
為此,我們需要選擇一個分割點,對我們來說是第三個提交,在 SHA 術語中是 9c68fdc
。因此,我們的基礎提交將基於該樹。我們可以使用 commit-tree
命令建立我們的基礎提交,該命令只需接受一個樹,然後會返回一個全新的、無父提交物件的 SHA-1 值。
$ echo 'Get history from blah blah blah' | git commit-tree 9c68fdc^{tree}
622e88e9cbfbacfb75b5279245b9fb38dfea10cf
注意
|
|

commit-tree
建立基礎提交好的,現在我們有了一個基礎提交,我們可以使用 git rebase --onto
在其之上變基我們剩餘的歷史記錄。--onto
引數將是我們剛剛從 commit-tree
獲得的 SHA-1 值,變基點將是第三個提交(我們希望保留的第一個提交的父提交,即 9c68fdc
)。
$ git rebase --onto 622e88 9c68fdc
First, rewinding head to replay your work on top of it...
Applying: fourth commit
Applying: fifth commit

好的,現在我們已經在一個一次性基礎提交之上重寫了我們的近期歷史記錄,該基礎提交現在包含了如果我們需要重新構建整個歷史記錄的說明。我們可以將這個新歷史記錄推送到一個新專案,現在當人們克隆該倉庫時,他們將只看到最新的兩個提交,然後是一個帶有說明的基礎提交。
現在讓我們轉換角色,假設有人第一次克隆專案並想要完整的歷史記錄。在克隆了這個截斷的倉庫之後,要獲取歷史資料,就需要為歷史倉庫新增第二個遠端倉庫並抓取(fetch)資料。
$ git clone https://github.com/schacon/project
$ cd project
$ git log --oneline master
e146b5f Fifth commit
81a708d Fourth commit
622e88e Get history from blah blah blah
$ git remote add project-history https://github.com/schacon/project-history
$ git fetch project-history
From https://github.com/schacon/project-history
* [new branch] master -> project-history/master
現在協作者將會在 master
分支中擁有他們最近的提交,並在 project-history/master
分支中擁有歷史提交。
$ git log --oneline master
e146b5f Fifth commit
81a708d Fourth commit
622e88e Get history from blah blah blah
$ git log --oneline project-history/master
c6e1e95 Fourth commit
9c68fdc Third commit
945704c Second commit
c1822cf First commit
要將它們組合起來,你只需呼叫 git replace
,指定你想替換的提交,然後指定你想用哪個提交來替換它。所以我們想用 project-history/master
分支中的“第四個”提交來替換 master
分支中的“第四個”提交。
$ git replace 81a708d c6e1e95
現在,如果你檢視 master
分支的歷史記錄,它看起來會像這樣
$ git log --oneline master
e146b5f Fifth commit
81a708d Fourth commit
9c68fdc Third commit
945704c Second commit
c1822cf First commit
很酷,對吧?無需更改上游所有的 SHA-1 值,我們就能夠用一個完全不同的提交替換歷史記錄中的一個提交,並且所有常用工具(如 bisect
、blame
等)都會按預期工作。

git replace
組合提交有趣的是,它仍然顯示 81a708d
為 SHA-1 值,儘管它實際上使用的是我們用來替換的 c6e1e95
提交資料。即使你執行像 cat-file
這樣的命令,它也會顯示被替換的資料。
$ git cat-file -p 81a708d
tree 7bc544cf438903b65ca9104a1e30345eee6c083d
parent 9c68fdceee073230f19ebb8b5e7fc71b479c0252
author Scott Chacon <schacon@gmail.com> 1268712581 -0700
committer Scott Chacon <schacon@gmail.com> 1268712581 -0700
fourth commit
請記住,81a708d
的實際父提交是我們的佔位符提交 (622e88e
),而不是這裡所說的 9c68fdce
。
另一個有趣的事情是,這些資料儲存在我們的引用中
$ git for-each-ref
e146b5f14e79d4935160c0e83fb9ebe526b8da0d commit refs/heads/master
c6e1e95051d41771a649f3145423f8809d1a74d4 commit refs/remotes/history/master
e146b5f14e79d4935160c0e83fb9ebe526b8da0d commit refs/remotes/origin/HEAD
e146b5f14e79d4935160c0e83fb9ebe526b8da0d commit refs/remotes/origin/master
c6e1e95051d41771a649f3145423f8809d1a74d4 commit refs/replace/81a708dd0e167a3f691541c7a6463343bc457040
這意味著我們可以很容易地與他人共享我們的替換操作,因為我們可以將其推送到我們的伺服器,其他人可以輕鬆下載。這在我們這裡討論的歷史嫁接場景中並沒有那麼大的幫助(因為無論如何每個人都會下載兩個歷史記錄,所以為什麼要將它們分開呢?),但在其他情況下它可能會很有用。