-
1. 開始
-
2. Git 基礎
-
3. Git 分支
-
4. 伺服器上的 Git
- 4.1 協定
- 4.2 在伺服器上取得 Git
- 4.3 產生您的 SSH 公開金鑰
- 4.4 設定伺服器
- 4.5 Git Daemon
- 4.6 智慧 HTTP
- 4.7 GitWeb
- 4.8 GitLab
- 4.9 第三方託管選項
- 4.10 摘要
-
5. 分散式 Git
-
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 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
建立基本提交好的,既然我們現在有了一個基礎提交(base commit),我們可以使用 git rebase --onto
將其餘的歷史記錄重新設定基底於此。--onto
參數將會是我們剛從 commit-tree
取得的 SHA-1 值,而 rebase 的起點將會是第三個提交(也就是我們想要保留的第一個提交 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
,並指定您想要替換的提交以及您想要用來替換的提交。因此,我們想要將 master
分支中的「第四個」提交替換為 project-history/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
這表示我們可以很容易地與其他人分享我們的替換,因為我們可以將其推送到我們的伺服器,其他人也可以輕鬆下載。在我們這裡討論的歷史嫁接情境中,這並不是很實用(因為無論如何每個人都會下載這兩個歷史記錄,那又何必將它們分開?),但在其他情況下可能會很有用。