## 替換
Git 對象是不可改變的,但它提供一種有趣的方式來用其他對象假裝替換數據庫中的 Git 對象。
`replace`?命令可以讓你在 Git 中指定一個對象并可以聲稱“每次你遇到這個 Git 對象時,假裝它是其他的東西”。 在你用一個不同的提交替換歷史中的一個提交時,這會非常有用。
例如,你有一個大型的代碼歷史并想把自己的倉庫分成一個短的歷史和一個更大更長久的歷史,短歷史供新的開發者使用,后者給喜歡數據挖掘的人使用。 你可以通過用新倉庫中最早的提交?`replace`老倉庫中最新的提交來連接歷史,這種方式可以把一條歷史移植到其他歷史上。 這意味著你不用在新歷史中真正替換每一個提交(因為歷史來源會影響 SHA 的值),你可以加入他們。
讓我們來試試吧。 首先獲取一個已經存在的倉庫,并將其分成兩個倉庫,一個是最近的倉庫,一個是歷史版本的倉庫,然后我們將看到如何在不更改倉庫 SHA 值的情況下通過?`replace`?命令來合并他們。
我們將使用一個擁有 5 個提交的簡單倉庫:
~~~
$ git log --oneline
ef989d8 fifth commit
c6e1e95 fourth commit
9c68fdc third commit
945704c second commit
c1822cf first commit
~~~
我們想將其分成拆分成兩條歷史。 第一個到第四個提交的作為第一個歷史版本。 第四、第五個提交的作為最近的第二個歷史版本。

Figure 7-28.
創建歷史版本的歷史很容易,我們可以只將一個歷史中的分支推送到一個新的遠程倉庫的 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
~~~

Figure 7-29.
現在我們可以把這個新的?`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 提交對象。
~~~
$ echo 'get history from blah blah blah' | git commit-tree 9c68fdc^{tree}
622e88e9cbfbacfb75b5279245b9fb38dfea10cf
~~~
> ###### NOTE
> `commit-tree`?命令屬于底層指令。有許多指令并非直接使用,而是被?**其他的**?Git 命令用來做更小一些的工作。有時當我們做一些像這樣的奇怪事情時,它們允許我們做一些不適用于日常使用但真正底層的東西。更多關于底層命令的內容請參見?[底層命令和高層命令](http://git-scm.com/book/zh/v2/1-git-internals/_plumbing_porcelain)

Figure 7-30.
現在我們已經有一個基礎提交了,我們可以通過?`git rebase --onto`?命令來將剩余的歷史變基到基礎提交之上。`--onto`?參數是剛才?`commit-tree`?命令返回的 SHA 值,變基點會成為第三個提交(我們想留下的第一個提交的父提交,`9c68fdc`):
~~~
$ git rebase --onto 622e88 9c68fdc
First, rewinding head to replay your work on top of it...
Applying: fourth commit
Applying: fifth commit
~~~

Figure 7-31.
我們已經用基礎提交重寫了最近的歷史,基礎提交包括如何重新組成整個歷史的說明。 我們可以將新歷史推送到新項目中,當其他人克隆這個倉庫時,他們僅能看到最近兩次提交以及一個包含上述說明的基礎提交。
現在我們將以想獲得整個歷史的人的身份來初次克隆這個項目。 在克隆這個截斷后的倉庫后為了得到歷史數據,需要添加第二個遠程的歷史版本庫并對其做獲取操作:
~~~
$ 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`?等)也都奏效。

Figure 7-32.
有趣的是,即使是使用了?`c6e1e95`?提交數據來進行替換,它的 SHA-1 仍顯示為?`81a708d`。 即使你運行了?`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`?真正的父提交是?`622e882`?占位提交,而非呈現的?`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
~~~
這意味著我們可以輕而易舉的和其他人分享替換,因為我們可以將替換推送到服務器中并且其他人可以輕松地下載。 也許在歷史移植情況下不是很有用(既然每個人都樂意下載最新版本和歷史版本,為何還要拆分他們呢?),但在其他情況下仍然很有用。
- 前言
- Scott Chacon 序
- Ben Straub 序
- 獻辭
- 貢獻者
- 引言
- 1. 起步
- 1.1 關于版本控制
- 1.2 Git 簡史
- 1.3 Git 基礎
- 1.4 命令行
- 1.5 安裝 Git
- 1.6 初次運行 Git 前的配置
- 1.7 獲取幫助
- 1.8 總結
- 2. Git 基礎
- 2.1 獲取 Git 倉庫
- 2.2 記錄每次更新到倉庫
- 2.3 查看提交歷史
- 2.4 撤消操作
- 2.5 遠程倉庫的使用
- 2.6 打標簽
- 2.7 Git 別名
- 2.8 總結
- 3. Git 分支
- 3.1 分支簡介
- 3.2 分支的新建與合并
- 3.3 分支管理
- 3.4 分支開發工作流
- 3.5 遠程分支
- 3.6 變基
- 3.7 總結
- 4. 服務器上的 Git
- 4.1 協議
- 4.2 在服務器上搭建 Git
- 4.3 生成 SSH 公鑰
- 4.4 配置服務器
- 4.5 Git 守護進程
- 4.6 Smart HTTP
- 4.7 GitWeb
- 4.8 GitLab
- 4.9 第三方托管的選擇
- 4.10 總結
- 5. 分布式 Git
- 5.1 分布式工作流程
- 5.2 向一個項目貢獻
- 5.3 維護項目
- 5.4 總結
- 6. GitHub
- 6.1 賬戶的創建和配置
- 6.2 對項目做出貢獻
- 6.3 維護項目
- 6.4 管理組織
- 6.5 腳本 GitHub
- 6.6 總結
- 7. Git 工具
- 7.1 選擇修訂版本
- 7.2 交互式暫存
- 7.3 儲藏與清理
- 7.4 簽署工作
- 7.5 搜索
- 7.6 重寫歷史
- 7.7 重置揭密
- 7.8 高級合并
- 7.9 Rerere
- 7.10 使用 Git 調試
- 7.11 子模塊
- 7.12 打包
- 7.13 替換
- 7.14 憑證存儲
- 7.15 總結
- 8. 自定義 Git
- 8.1 配置 Git
- 8.2 Git 屬性
- 8.3 Git 鉤子
- 8.4 使用強制策略的一個例子
- 8.5 總結
- 9. Git 與其他系統
- 9.1 作為客戶端的 Git
- 9.2 遷移到 Git
- 9.3 總結
- 10. Git 內部原理
- 10.1 底層命令和高層命令
- 10.2 Git 對象
- 10.3 Git 引用
- 10.4 包文件
- 10.5 引用規格
- 10.6 傳輸協議
- 10.7 維護與數據恢復
- 10.8 環境變量
- 10.9 總結
- A. 其它環境中的 Git
- A1.1 圖形界面
- A1.2 Visual Studio 中的 Git
- A1.3 Eclipse 中的 Git
- A1.4 Bash 中的 Git
- A1.5 Zsh 中的 Git
- A1.6 Powershell 中的 Git
- A1.7 總結
- B. 將 Git 嵌入你的應用
- A2.1 命令行 Git 方式
- A2.2 Libgit2
- A2.3 JGit
- C. Git 命令
- A3.1 設置與配置
- A3.2 獲取與創建項目
- A3.3 快照基礎
- A3.4 分支與合并
- A3.5 項目分享與更新
- A3.6 檢查與比較
- A3.7 調試
- A3.8 補丁
- A3.9 郵件
- A3.10 外部系統
- A3.11 管理
- A3.12 底層命令