为什么'git rebase'保留了重新分支的历史?

时间:2012-09-17 18:26:53

标签: git git-rebase

据我所知http://www.kernel.org/pub/software/scm/git/docs/git-rebase.html,重新分支的分支被“移动”到另一个分支。

但是,我在测试中看到的情况表明,重新分支的提交仍然保留在历史记录中,因此它们实际上是重复的。

Before rebase:

After rebase:

也许我遗漏了某些东西,或者完全不了解rebase的目的或两者兼而有之。

如果我看到的是预期的行为,那为什么会这样呢?

4 个答案:

答案 0 :(得分:5)

简而言之,rebase是一种将提交从树的一部分应用到不同起点的方法。它可以复制这些更改,但不会移动它们。

请记住,git提交是不可变的 - 一旦有东西有哈希值,它就永远不会改变。这意味着当你在另一个变化之上重新定义一些变化时,散列必然是不同的,所以git将保持旧的和新的变化。

但是,如果没有分支名称指向旧提交(在您的示例中为“add file2”),那么几周后git的自动垃圾收集器将从您的存储库中删除旧提交。 (为什么两周?这样,如果你改变主意,你可以从git reflog检索旧的提交。)一般来说,这是一件好事 - 它会使意外丢失数据变得更加困难 - 但如果该文件非常庞大,您可以使用git prunegit gc的组合来删除冗余数据。

答案 1 :(得分:5)

这里有两种不同的现象。

  1. 您发布的屏幕截图,来自gitk,仍显示旧提交。这就是gitk的工作方式;如果你通过点击 Ctrl + F5 重新加载,而不仅仅是 F5 (那是文件>重新加载而不是文件>为鼠标用户更新)你会看到旧的提交消失了,因为它不再相关。

  2. Git中有很多的操作可以创建提交。甚至更多的是在文件存储中创建文件或树对象。许多这些对象不再使用这一事实无关紧要。

    这有很多优点。在您的示例中,这意味着如果您认为您的rebase是一个坏主意,您的旧提交仍然存在并且可以恢复。甚至还有一个方便的语法:topic@{1}指的是topic在上一次移动之前指向的提交;在这里,这将是在rebase之前。

    Git对象模型对这类事情很聪明。像这样的额外提交会占用非常一点额外的空间。对于像你所描述的那样的rebase,我希望保留旧分支最多花费几百个字节。

    当然,随着时间的推移,这确实会增加。所以git gc(每隔一段时间由某些命令自动运行)运行git prune。并且git prune会查找旧的且不再相关的提交和对象,并为您清除它们。

  3. 这并不意味着你的rebase没有起作用,只是因为rebase“移动”提交的想法是简化。 rebase实际上做的是将每个提交及其父级之间的差异应用于新分支,并为旧分支上的每个提交创建一个具有这些差异的新提交。然后它会更新分支,这样,如果查看分支历史记录,就好像这些提交已被移动一样。

答案 2 :(得分:1)

Rebase是一个重写历史记录的命令。但是感谢git,你的历史不会丢失。你可以回滚,直到git垃圾收集器清除那些悬空提交。

答案 3 :(得分:1)

......重新分支的分支被“移动”到另一个分支。

这是摆弄它的一种方式,但不完全准确。

考虑git repo的最佳方式是将其视为两件事的组合:一个有向的,不可变的提交的非循环图,每个代表一个版本的软件(或者repo中的任何东西),以及分支指针变量集(等)。

假设您从一个包含三个提交的repo开始,如下所示:

a--> b
 \-> c

其中 origin / master 分支指针指向b master 分支指针指向c。您实际上在这里有三个不同版本的软件,abc

如果您决定将c重新定位到b,那么您最终会得到一个如下所示的回购:

a--> b--> c'
 \-> c

分支指针更改为指向c'。 “提升此提交”将导致提交c'被发送到原始仓库,原始仓库的分支指针被更改为指向c',并且您的 origin / master 分支指针被更改为匹配它。

您会注意到c'是与c不同的提交,它仍然存在,您现在有四个版本的软件。 c'提交在道德上对bc所做的a进行了相同的更改(或者有人希望,假设您已恰当地编辑了任何冲突)。

c不再有任何指向它的分支指针(实际上,在reflog之外),因此在正常的git操作期间稍后会被垃圾收集。

(Git还会执行一些花哨的压缩技巧来存储所有这些不同[和完整]版本的软件,而不是单独检查它们,但这不是你需要的,甚至不应该,打扰想着。)

在随意的谈话中,我们将此操作称为“更改分支”,但实际上,您正在做的是创建新分支并更改 master 指从旧分支到新分支。