是否可以删除Git中的旧提交而不会丢失数据?

时间:2015-09-22 18:21:56

标签: git mercurial rebase

我们正在从Mercurial迁移到Git。

在这个过程中,我想对一些较旧的,较大的存储库进行一些内务管理。

我们有一个具有近5年历史的特定项目,并在其中承诺。

我看不到任何需要我们在3年前恢复提交的用例。

此特定项目还有一个提交,发生在4年前,其中开发人员提交了超过200,000个小文本文件,这些文件在一系列测试中使用。这一数量的文件破坏了我们系统的性能。因此,稍后提交了一些提交这些文件。虽然这有助于本地系统的整体性能,但所有这些文件仍包含在存储库历史记录中。

我的目标是摆脱这些文件以及克隆此存储库时导致的整体膨胀。

所以我想学习的是,如果有一种方法可以在Git中有效地修改我们历史中的旧提交,而不会丢失之前提交中所做的更改?换句话说,将存储库中的第一个提交重置为工作文件夹在特定时间点的内容?

编辑:由于我担心删除因添加和后来删除大量文件而导致的膨胀,我不认为这是Remove an old Git commit from a branch without using a reverse patch?的直接复制 - 但是解决方案可能会转变出来是一样的(我现在还不知道)

2 个答案:

答案 0 :(得分:3)

与许多其他版本控制系统不同,Git不存储增量。 1

这意味着每个提交在重要意义上完全独立于其他提交。这意味着您可以自由选择"拔出"任何提交,不影响任何其他提交,只要您知道自己在做什么。

在每个前任提交中,它有一个完全依赖的重要意义,但就SHA-1而言,这是真正的" true name",而不是与之关联的源树。换句话说,只要你知道自己在做什么,这对你没有影响。 2

至于如何删除特定的提交,你有几个选择。与您链接的问题相关的答案使用交互式rebase。这可以工作,虽然它只处理更简单的情况(一个分支,一个大文件或一组必须只删除一次的文件,这类事情)。你需要知道的是git rebase -i基本上git cherry-pick类固醇,因为它是:它自动完成一系列樱桃挑选操作,然后做一些简单的分支标签操作。

另一种方法是使用git filter-branch。在这种情况下,这可能是更正确的方法。这里要知道的是git filter-branch有点类似git rebase类固醇,因为它是:它自动化许多复制操作(不是特别挑选),然后做复杂的,多标签操作(分支,也可选择标签)。

让我有一个脚注休息,然后我会告诉你关于过滤器分支你需要了解的内容。

1 Deltas回到via"打包文件",这给git提供了良好的压缩效果(比许多其他VCS-es更好),但这些发生在远低于git在每次提交时都存储一个树。就提交而言,每个提交只是一个带有一些元数据的对象和一个(单个)"树"对象,并且树包含与该提交一起使用的文件的完整,独立的快照。当你git show提交并且一个delta时,那是因为git不仅提取了那个特定的提交,还提取了它的父提交,然后是-at { {1}}时间使用它的差异生成器向你显示该提交中发生的事情,关于那个父母或那些父母。

2 当然,如果你不确定自己在做什么,这会留下很多摆动空间。 :-)特别是,无论你在这里做什么,你都会结束"重新编号"所有提交"下游"任何被修改的提交。 如果其他人已经拥有这些提交的副本(例如,当前回购的克隆),他们将不得不采取一些行动来更新他们的副本,所以你&# 39;要为他们做一堆工作。如果"他们"包括"你" -ie,如果你有几份原始回购 - 你必须自己做一些事情,但这可能只是"扔离开那些副本并获得新的副本",你可以按自己的节奏做。你不会让自己烦恼,或者至少,你知道它什么时候。 : - )

回到git show:它的作用与几乎所有其他git命令完全相同。它不会 - 可以不改变任何现有的提交。相反, copy 提交,通过提取它们,然后应用一些过滤器,然后进行新的提交。

您应该将git存储库视为一大堆"对象",包括提交对象,每个提交看起来像这样:

git filter-branch

每个提交可以有任意数量的标签(通常是分支和标签名称)&#34;指向&#34;提交。标签&#34;指向&#34;提交与提交&#34;指向&#34;的方式相同它的父母和树,通过列出SHA-1&#34;真名和#34;那个对象。 (其他对象类型是&#34;树&#34;,&#34; blob&#34;和&#34;带注释的标签&#34;。所有对象都在&#34;内部&#34;回购,在.git / objects中,而标签更多地围绕着repo的边缘&#34;在.git / refs中。像tree 55c0d854767f92185f0399ec0b72062374f9ff12 parent 8413a79e67177d026d2d8e1ac66451b80bb25d62 author Junio C Hamano <redacted> 1436563740 -0700 committer Junio C Hamano <redacted> 1436563740 -0700 The last minute bits of fixes Signed-off-by: Junio C Hamano <redacted> 这样的一些特殊标签直接在HEAD本身确切的位置并不重要:这里的关键是标签指向提交,然后让你或git在repo中启动。然后根据需要提交指向其他提交。)

这是git git repo内部提交的实际内容(修改为取出电子邮件地址以便垃圾邮件发送者不收集它们)。此提交的SHA-1由其内容确定 - .git/tree值,parentauthor名称和时间戳以及消息。在某些时候,committer命令将提取此提交,应用您的过滤器,然后从结果中进行新的提交。

filter-branch命令提供了大量过滤器,以便您可以使用尝试提高效率的变体更改每个提交的任何或所有部分。复制修改后的提交最慢的部分通常是提取所有旧文件,然后检查结果并创建新文件,有时你可以制作一个完全在&#34; index&#34;中运行的过滤器,跳过提取 - 并检查步骤。原则仍然是相同的:检查临时目录中的旧提交;然后用过滤器修改它;然后从结果中进行新的提交。

每次新提交都会获得一个新的SHA-1&#34;真实姓名&#34;。

如果新提交与旧的commit-bit-for-bit相同,则新的SHA-1与旧的SHA-1相同。出于过滤器分支的目的,这并不重要:因为它随着复制提交,它会更新一个&#34; map&#34;文件。映射文件保留了一对值:old-SHA-1,new-SHA-1。每次脚本复制提交时,都会确保&#34; parent&#34;指针查找适当的映射,以便 new 提交指向 new 父项,而旧提交继续指向旧父项(因为它们必须)。

最终 - 这可能需要很长时间,这就是有这么多优化标志的原因 - git filter-branch会将过滤器应用于您要求它查看的所有提交。此时,需要将地图文件应用于标签。

同样,标签是你和git本身的开始。如果您要在分支filter-branch上查找提交,请先查找标签master。它包含提交的SHA-1真实名称:根据定义,该提交是分支master的提示。这个承诺有一些父母,那些承诺有他们自己的父母,等等;并且git将根据需要通过读取这些提交来动态构建提交图。

因此,filter-branch命令现在只需要将所有旧标签更改为指向新提交,而不是指向旧提交。

master重写的标签是您在命令行中命名的标签。对于这种事情,你要命名git filter-branch,这意味着所有分支。实际上,--all表示所有引用,但--all将其除去只是分支,除非添加git filter-branch。 (我不完全确定git人们在这方面考虑过哪些用例;大多数人只是使用--tag-name-filter来保持标签名称不变,同时更新它们以指向新复制的提交。 )

搜索StackOverflow以获取有关使用(和加速)--tag-name-filter cat的更多信息。我不确定它是否适用于您的特定情况(我自己从未使用过),但也考虑使用&#34; BFG repo清洁剂&#34;,这是一个加速剥离-down git filter-branch表示删除不需要的文件的具体情况。它的设置要简单得多,因为它不会应用任意过滤器。当然,它确实具有相同的警告,因为从根本上说,提交可以从不进行更改,您可以做的最好的事情是制作相似但不同的新副本,从而具有不同的SHA-1 &#34;真名&#34;。

答案 1 :(得分:0)

从Mercurial方面删除这些更改集可能会更简单,更安全(您可以始终从头开始使用原始仓库,而不是修剪克隆):

只是histedit并删除changeset,它会添加文件和提交,以便稍后处理这些文件