是否可以使用交互式git rebase从历史记录中删除一个文件?



我的本地存储库中有一个旧的提交,其中添加了一些文件,包括一个名为" wanted.txt"的文件。在随后的提交中,该文件和其他文件一起被修改。是否有可能完全删除文件"不想要的。txt"从历史使用交互式git rebase?我知道可以使用"git filter-branch"来实现这一点,但是由于我正在学习git,并且我想了解"git rebase -i"的全部潜力,我想知道这个命令是否可以用于这样的操作。

您应该能够通过编辑违规提交(即在rebase待办事项列表中提交前的eedit)来做到这一点,然后像这样删除文件:

git rm unwanted.txt
git commit --amend
git rebase --continue

这可能会在以后的提交中给您带来改变文件的冲突,但这应该通过再次删除文件并继续rebase来轻松解决。

编辑:您很可能还必须确保没有分支指向仍然存在不需要的文件的任何提交,并运行git gc以清除repo中的未引用blobs。如果它是一个纯私有的不与任何人共享的repo,这应该不是问题。

这在理论上是可能的,但在实践中通常太痛苦了。

方法在rebase和filter-branch中是相同的。如果你意识到所有的交互重基,都是类固醇的git cherry-pick,可能会有所帮助;git filter-branch只是一个跨多个分支和合并保存的自动的额外复杂的重置。

与git通常一样,它主要归结为操作提交图,并添加看起来像现有提交但有所更改的新提交——在本例中,是附加到这些提交的树。(只要有一个提交是不同的,它就会得到一个不同的SHA-1,这意味着所有的后续的提交也必须改变,以列出随着新图的增长而出现的不同的SHA-1。)

要了解它是如何工作的,首先绘制提交图。您将需要一个相当完整的图形,这取决于您必须返回到停止查看unwanted.txt文件的时间。但我只画一个简单的图,只有一个命名为master:

的分支
I - A - B - C - F   <-- master
             /
        D - E

这里的I是初始提交;为简单起见,假设没有包含不需要的文件。让我们假设这个文件是在commit A中引入的,并在CE中进行了修改。

我们需要做的是:

  1. 复制所有的提交I(保留提交作者和提交者,以及日期戳,等等),同时删除不需要的文件,即,如果需要,修改附加到I的源代码树。这只是给我们提交I,所以我们保留原来的SHA-1。
  2. 在删除不需要的文件的同时复制所有的commit A。这将导致一个新的、不同的提交A',因为我们将A的树更改为一个删除了文件的新树。我们得到一个新的SHA-1加密校验和,因为新的提交与旧的提交不同。所以我们在map中保存一个条目,上面写着"旧的commit A被新的commit A'取代"。
  3. 复制所有的提交B同时删除不需要的文件。这将更改树(记住,每次提交都有整个源的完整快照,因此不需要的文件位于原始B中)。创建一个新的commit B',修改树以commit A'作为父ID。
  4. 复制所有提交C,同时删除不需要的文件,导致C'
  5. 复制所有的提交D与我们的变化,导致D'。(注意,我们不能复制F,直到我们复制了图中所有的前身,在本例中是CE。)
  6. 复制所有的commit E
  7. 复制所有的commit F和我们的更改。新的commit F'C'E'作为它的双亲;我们使用我们一直在构建的SHA-1映射找到这些。
  8. 最后,修改master指向提交F',放弃原来的提交F

结果如图所示:

    A - B - C - F    [abandoned]
   /         /
  /     D - E
 /
I - A' - B' - C' - F'   <-- master
                /
         D' - E'

带有--preserve-merges的交互式rebase可以处理这种特殊情况。但是,如果有多个分支,则必须根据需要使用--onto仔细地重新设置其他分支的基,以便使用新的提交,您必须将新提交与旧提交相匹配,最有可能使用您手动构建的SHA-1映射文件。

还有一个额外的问题,即git commit默认情况下拒绝进行"空"提交,其中"空"定义为"与前一次提交具有相同的树"(并且不是合并)。过滤器分支脚本会自动为您处理这个问题,如果您选择删除空提交,则将多个新提交映射到单个旧提交(当前一个和新提交都放弃了不需要的文件时,仅修改了不需要的文件的提交将变为空)。在保留合并时,交互式重基不能很好地处理这个问题,因此这会带来更多的痛苦。

还有其他一些微妙的区别:例如,当rebase"放弃"一个提交链时,它们仍然在被rebase的分支的"reflog"中,以及在HEAD的reflog中。过滤器分支脚本使用不同的方法:它将所有引用复制到子名称空间refs/original/。当您想要清除旧的、被放弃的提交时,这一切都很重要:使用rebase,您将"过期"旧引用,但使用filter-branch,您将强制删除原始引用。

在我的情况下,我必须'添加'文件才能在其上使用git rm -f。这是因为当执行重基和提交删除时,目录中不再存在文件。

git add path/to/file
git rm -f path/to/file

如果你想删除整个文件,你也可以这样做:

rm unwanted.txt
git add unwanted.txt
git commit -m "remove unwanted file"
git rebase -i HEAD~<number_of_commit ahead>

,然后在交互式重置中,你移动并修复"删除不需要的文件"像这样提交:

pick e33adb3 commit with unwanted file
f 095204f remove unwanted file
pick 54caa68 another commit
pick 6e03883 another commit
pick aa3d754 another commit

最新更新