我知道,我知道,重写历史是可怕的,但是......
我不小心检查了一个大的sql文件来git我需要删除所有历史记录。
我显然没有做正确的事情,但我按照以下方式运行:
git filter-branch --force --index-filter \
'git rm --cached --ignore-unmatch dump.sql' \
--prune-empty --tag-name-filter cat -- --all
它成功删除了文件,但导致事情处于一种奇怪的状态:
知道如何解决这个问题吗?是否有些日期因提交导致这种奇怪的行为而被更改?
答案 0 :(得分:0)
好的,根据评论,我想我现在可以正确描述问题了。
(这种情况发生在任何类型的历史记录重写中,但如果它是非共享存储库,或者它的所有用户都愿意更新,则可以在此成功使用git filter-branch
。)
您需要知道的起点是git 永远不会更改提交,不会更改git rebase
,不会更改git filter-branch
,甚至不会更改git commit --amend
}。为了使事情看起来发生变化,这些命令的作用是基于旧的提交 new 提交,但改变了一些东西。
然后,他们移动标签,以便您看到新提交而不是旧提交。
这是一个包含两个分支和五个总提交的存储库的简单示例:
B <- C <-- br1
/
A
\
D <- E <-- br2
提交A
是初始(根)提交,然后有两个分支结构,包括提交B
和C
(标记为br1
)和{{1 }和D
(标记为E
)。 (这里的每个字母代表一些大毛茸茸的SHA-1,它是&#34;提交的真实姓名&#34;例如,br2
实际上可能是A
。)
每个提交都指回其祖先,因此004257f...
指向C
,B
指向B
。没有&#34;前进指针&#34;:A
不指向A
或 B
。 D
和B
都是D
的后代的事实必须是计算,从终点开始并向后工作。 (这是在不更改A
的情况下添加提交D
的方式。)
接下来,您需要知道&#34;分支&#34;在git中有两个不同的含义:我称之为&#34;结构分支&#34;,你可以看到A
是一个分支而A <- B <- C <-- br1
是另一个分支。 (这两个分支在A <- B <- C <-- br2
分享一个共同点,因此您可以说A
仅包含br1
和C
,B
仅包含br2
{1}}和E
。但是那个分支是D
&#34;&#34;?它真的&#34; on&#34; 两者,从某种意义上说,如果在A
之前有任何提交,那么他们也会在#34;两者之间#34;有时候更清楚地谈论分支&#34;包含&# 34;提交,并且有A
来显示包含给定提交的分支的标签。)
Git的标签 - 分支和标签名称之类的东西,虽然&#34;远程分支&#34;也只是标签,就像git branch --contains
的{{1}}引用之类的东西一样,开始了:标签都是按名称查找的,每个标签都给出了原始的SHA- 1表示提交 1 。因此stash
具有提交git stash
的原始SHA-1。从那里开始,git也会找到br1
,然后找到C
。分支标签B
找到A
,其前往br2
,然后发送到E
。
分支标签也称为&#34;分支&#34;,但从技术上讲,它们只识别分支的 tip 。也就是说,D
使A
成为&#34;结束&#34;分支。
(请注意,如果我们添加一个指向br1
的标签 - 比如说,添加C
- 我们只是添加了另一个分支。这适用于两种意义:那里有一个新的分支标签,B
和一个新的&#34;结构分支&#34;,包含提交master
和master
。这与此无关重叠B
;它使A
成为分支提示。或者,我们可以br1
指向B
,以便分支master
和{ {1}}完全重叠。如果我们然后向C
添加提交br1
,则分支提示master
向前移动以指向F
但是分支提示{ {1}}仍然落后,指向br1
。这个分支尖端的自动推进是指在分支上的意思#34;你只能在一个分支标签上,甚至如果许多标签都指向一个提交,那么那就是前进的分支标签。)
现在让我们看一下&#34;历史重写的简单案例&#34;,我们进入分支br1
并执行F
。这里的想法是选取当前未与master
共享的C
部分 - 即提交br2
和git rebase br1
- 以及&#34;移动&# 34;他们,导致:
br2
(我已经从内部提交链接中删除了箭头,因为它们很难绘制。)但这是一张不准确的图片。 Git实际上并没有移动提交;这需要更改br1
。原始提交D
记录其父提交(或提交,但这里只有一个),以及E
。 Git无法更改 B - C <-- br1
/ \
A D - E <-- br2
- 但它可以将复制到新的提交D
,除了D
说&#外,所有内容都相同34;我的父母是A
&#34;:
D
一旦这样做,git可以将D'
复制到D'
,其中C
的所有内容都相同,除了(再次)其父级: B - C <-- br1
/ \
A D'
\
D - E
& #39;父母是E
:
E'
现在所有git需要移动标签E'
。标签用于指向E'
;如果它现在指向D'
而没有指向 B - C <-- br1
/ \
A D' - E'
\
D - E
,则提交br2
和E
成为&#34;被放弃&#34;并且可以在以后进行垃圾收集:
E'
所以,E
基本上是&#34; git rebase on steriods&#34;原样。它不是仅复制少数提交,而是复制所有提交,在每次新提交之前应用提供的过滤器。鉴于我们原始的简单D
- 通过 - E
,如果您执行 B - C <-- br1
/ \
A D' - E' <-- br2
\
D - E [abandoned]
,则会复制每次提交。
如果你的过滤器没有做任何更改,那么新复制的提交都是完全相同的(每个文件是相同的,每个树是相同的,每个提交是相同的,所以它们都结束了使用相同的原始SHA-1 ID并且没有任何标签移动)。但实际上你的过滤器必须做出一些改变,可能在历史上已经相当长了。在这种情况下,我们假设它改变了提交git filter-branch
的内容,因此git必须改为复制A
。 filter-branch的结果如下:
E
(而不是仅仅放弃以前的分支,过滤分支的叶子标签拼写为git filter-branch --all
,如果结果符合您的要求,您可以将其删除。)
请注意,它会从变更点向前复制所有内容。但它只在您的本地存储库中完成此操作。你提到你有一个&#34;远程拷贝&#34; (大概是A
)。 他们只有原件,没有副本。
让我们绘制一个更真实的提交图,其中A'
个节点用于不感兴趣的点和更复杂的历史记录。让我们进一步假设要删除的 B - C <-- refs/original/heads/br1
/
A
\
D - E <-- refs/original/heads/br2
B'- C' <-- br1
/
A'
\
D'- E' <-- br2
文件发生在分支refs/original/...
和origin
分割之前:
o
此处dump.sql
是第一个包含不需要的文件的提交。 (我们很快就会看到为什么我很快就标记了branch_a
和branch_b
。)因此,filter-branch命令必须将其复制到--o--W--X--Y--o--o--A <-- branch_a
\
o--o--B <-- branch_b
,以及所有后续提交,包括两个分支提示X
和W
:
Y
现在,你说你已经删除了X'
&#34;的远程副本。也就是说,你做了A
。
这里有点复杂。 : - )
B
仅为--o--W--X--Y--o--o--A <-- [some label(s), incl refs/original/...]
\ \
\ o--o--B <-- [some label(s)]
\
X'-Y'-o'-o'-A' <-- branch_a
\
o'-o'-B' <-- branch_b
,后跟branch_b
。 git checkout branch_a; git pull origin branch_b
步骤转到指定的远程并带来任何新提交。可能没有新的提交 - 你已经拥有所有旧的提交,你从中复制了 - 但 merge 步骤在这里出错了。
要考虑此抓取和/或较早的抓取,其中包含&#34;某些标签&#34;以上,我们也添加git pull
标签。有git fetch
指向提交git merge
,git fetch
指向origin/
。同样,这些是旧提交。
因此,origin/branch_a
找到提交A
和origin/branch_b
的合并基础。这就是他们的历史分叉的地方,即B
之前的提交,即提交git merge
。然后,它会合并A'
与合并基础B
和&#34;他们的&#34;分支X
,查看W
和A'
之间的变化,以及W
和B
之间的变化。
无论它做了什么,这都会带回不需要的W
,因为这是从B
到W
添加的,而不是从A'
开始添加的到dump.sql
。它还会选择下面的两个提交,我将W
替换为B
。因此,此时合并的树中包含新的W
,您将获得将A'
绑定到o
的合并提交:
*
现在让我们说你要求dump.sql
合并到branch_a
:
origin/branch_b
Git再次需要找到合并基础。 (或者,如果--o--W--X--Y--o--o--A <-- origin/branch_a
\ \
\ *--*--B <-- origin/branch_b
\ `--.
X'-Y'-o'-o'-A'-M <-- branch_a
\
*'-*'-B' <-- branch_b
和branch_a
之间存在纵横交错,使用递归策略,它必须创建一个&#34;虚拟合并基础&#34;通过组合同样好的祖先,但是让我们保持简单。)在这种情况下,合并基础是branch_b
。我们将从git checkout branch_b; git merge branch_a
branch_a
获取更改,即branch_b
提交的副本,再加上Y'
。但我们已经进行了这些更改,因为我们合并了Y'
,它具有相同的更改(加上branch_b
)。
通常情况下,这只会工作&#34;但可能在你的情况下,事情变得更加复杂,你会遇到一堆冲突。
在任何情况下,事情都会出错,因为你正在复活旧的&#34; (预先&#34;重写&#34;)使用*
上的分支进行分支。
这就是为什么很难重写历史记录&#34;成功:你可以很容易地重写自己的,但是如果你已经在其他地方发布了它(例如,在B'
上),其他人就有了早期的历史,你就不能强迫他们摆脱它。你必须让他们自愿放弃旧的&#34;错误&#34;历史并切换到新的&#34;权利&#34;历史。
1 或其他git对象,如带注释的标签;或标签可以是间接引用,即包含另一个标签的名称。通常只有特殊的origin/branch_b
标签是间接引用,包含您所依赖的分支的名称。