可能破坏/重写历史的 Git 命令

Posted

技术标签:

【中文标题】可能破坏/重写历史的 Git 命令【英文标题】:Git commands that could break/rewrite the history 【发布时间】:2014-11-01 10:29:35 【问题描述】:

您能否提供(所有或最常见的)可能破坏 git 历史记录的操作或命令的列表?

应该绝对避免什么?

    在推送此提交后修改提交 (git commit/git push/git commit --amend) 重新定位到已经推动的东西

我希望这个问题(如果它在其他地方之前没有问过的话)成为对 git常见可避免操作的某种参考。

此外,我经常使用git reset,但我并不完全意识到我可能对存储库(或其他贡献者的副本)造成的损害。 git reset会有危险吗?

【问题讨论】:

【参考方案1】:

请注意,从 Git 2.24(2019 年第四季度)开始,上面的列表可能不再需要包含 git filter-branch

git filter-branch 已被弃用(BFG 也是)

参见Elijah Newren (newren) 的commit 483e861、commit 9df53c5、commit 7b6ad97(2019 年 9 月 4 日)。(由 Junio C Hamano -- gitster -- 合并到 commit 91243b0,2019 年 9 月 30 日)

推荐 git-filter-repo 而不是 git-filter-branch

filter-branch 遭受大量变相的危险,毁容 历史重写(即偏离故意的改变)。

其中许多问题并不引人注目,很容易被发现,直到 新存储库正在使用中。 这可能会导致一系列问题,从导致人们首先访问filter-branch 的历史更加混乱,到数据丢失或损坏。这些问题无法向后兼容修复,因此请在 filter-branch 及其手册页中添加警告 建议改用其他工具(例如 filter-repo)。

另外,更新其他引用 filter-branch 的联机帮助页。 即使我们可以继续推荐,其中一些也需要更新 filter-branch,要么是因为暗示某些东西是独一无二的 filter-branch 当它更普遍地应用于所有历史重写时 工具(例如BFGreposurgeonfast-importfilter-repo),或者因为 关于filter-branch 的一些东西被用作示例,尽管还有其他更多 众所周知的例子现在已经存在。 改写这些部分以解决这些问题并避免推荐filter-branch

最后,删除将BFG Repo Cleaner 解释为 替代filter-branch。 我对此感到有些不好,特别是因为我觉得我从 BFG 学到了很多东西,以至于我在filter-repo 中得到了很好的利用(这比我能说的要多得多) filter-branch),但保留该部分会带来一些问题:

为了建议人们退出使用 filter-branch,我们需要向他们推荐可以处理所有相同类型重写的其他东西。 据我所知,filter-repo 是唯一这样的工具。所以需要提一下。 我不想向用户提供相互矛盾的建议 如果我们推荐两种工具,我们不应期望用户同时学习这两种工具并选择使用哪一种;我们应该解释一个人可以解决哪些问题而另一个人不能解决,或者什么时候一个人比另一个人快得多。 BFGfilter-repo 具有相似的性能 BFG 可以做的所有过滤类型,filter-repo 也可以做。 事实上,filter-repo 带有一个名为 bfg-ishBFG 的重新实现,它提供与 BFG 相同的用户界面,但由于其技术基础,在 BFG 中难以实现的几个错误修复和新功能.

虽然我仍然可以提到这两个工具,但似乎我需要提供某种比较,我最终只会说filter-repo 可以做所有BFG 可以做的事情,所以最终它似乎更好完全删除该部分。


哪些操作或命令会破坏 git 中的历史记录?

至少,newren/git-filter-repo 可以恢复任何因使用而受损的历史记录。

在其既定目标中:

更智能的安全

将原始 ref 的副本写入 repo 中的特殊命名空间不提供用户友好的恢复机制。许多人将难以使用它来恢复。

我见过的几乎每个执行存储库过滤操作的人都使用新的克隆来执行此操作,因为在出​​现错误时清除克隆是一种非常容易的恢复机制。 如果我们不在新克隆中,则通过检测和放弃来强烈鼓励该工作流程,除非用户使用--force 覆盖。


git filter-repo as mentioned in the documentation 大致可以运行:

git fast-export <options> | filter | git fast-import <options>

并且git fast-export / git fast-import 在 git 2.24(2019 年第四季度)方面有所改进

参见commit 941790d、commit 8d7d33c、commit a1638cf、commit 208d692、commit b8f50e5、commit f73b2ab、commit 3164e6b(2019 年 10 月 3 日)和commit af2abd8(2019 年 9 月 25 日)@9876 .(2019 年 10 月 15 日由 Junio C Hamano -- gitster -- 在 commit 16d9d71 中合并)

例如:

fast-import: 允许标记标签识别标签

签字人:Elijah Newren

fast-exportfast-import 中使用标记标识符来提供标签以引用早期内容。

Blob 被赋予标签是因为它们需要在提交中以给定文件名首次出现时被引用,而提交被赋予标签是因为它们可以是其他提交的父级。

标签从未被赋予标签,可能是因为它们被认为是不必要的,但这带来了两个问题:

    如果我们想创建一个标签的标签(或更高的嵌套),它让我们无法引用以前的标签。 使用--export-marks--import-marks 时,我们无法记录标签已经导入。

通过允许标签的可选标记标签来解决这些问题。

【讨论】:

它在纸上看起来很棒,但任何时候我都会收到以下错误 Error: need a version of git whose diff-tree command has the --combined-all-paths option,使用 git 版本 2.17.1 @J.M.Janzen 奇怪,我没明白(但我确实使用 Git 2.23)【参考方案2】:

在我的头顶:

git commit --amend 会重写之前的提交 git rebase 可以重写多个提交(使用带有--rebase 标志或branch.$name.rebase 配置选项的git pull 时也会调用rebase) git filter-branch 可以重写多个提交 git push -f 可以更改分支指向的提交(git push origin +branch 语法也是如此) git reset 可以更改分支指向的提交 git branch -f 可以更改分支指向的提交(通过重新创建具有相同名称的分支) git checkout -B 可以更改分支指向的提交(通过重新创建具有相同名称的分支)

【讨论】:

git pull --rebase 也是。 嗯,不错的清单。我应该更好地学习很多命令。您能否至少更深入地解释一下git reset 案例?任何git reset(带/不带--soft--hard)都可以破坏它吗?一个实际的例子? @Kamafeather:git reset $commit 将更改当前分支的尖端,而不管使用的标志(软/硬/混合/保持/合并/...)。例如:git reset HEAD^; git push -f - 这将从发布的历史记录中删除最后一次提交(服务器上的结果与git push -f origin HEAD^:HEAD 无法区分) @Kamafeather git svn dcommit 还会更改历史中尚未提交到 subversion 存储库的每个提交。【参考方案3】:

knittl 已经编写了一个很好的重写历史命令的列表,但我想以他的回答为基础。

您能否提供一份 [...] 可能破坏 git 历史记录的操作或命令的列表?应该绝对避免什么?

首先,重写/删除历史并没有错本身;毕竟,您可能会定期创建功能分支,将它们严格地保留在本地,然后删除(在合并它们或意识到它们无处可去之后),而不会三思而后行。

但是,当您在本地重写/删除其他人已经访问的历史记录然后将其推送到共享远程时,您可以而且肯定会遇到问题。

应该算作重写/删除本地仓库历史的操作

当然,有一些愚蠢的方法可以破坏或删除历史记录(例如篡改 .git/objects/ 的内容),但这些不在我的回答范围内。

您可以通过多种方式重写本地存储库的历史记录。 Pro Git 书籍中标题为Rewriting history 的部分提到了一些

git amend --commit git rebase git filter-branch Roberto Tyley 的 BFG Repo Cleaner(第 3 方工具)

可以说,还有更多。任何有可能改变或以其他方式移动非符号引用(分支或标签)并使其指向一个提交的操作,该提交不是是分支当前提示的后代,都应计为重写本地历史。这包括:

git commit --amend:替换最后一次提交; 所有形式的变基(包括git pull --rebase); git reset(参见下面的示例); git checkout -Bgit branch -f:将现有分支重置为不同的提交; git tag --force:重新创建一个同名但可能指向另一个提交的标签。

任何非符号引用(分支或标签)的删除也可能被视为历史删除:

git branch -dgit branch -D git tag -d

可以说,删除一个已经完全合并到另一个分支中的分支应该被认为只是一种温和的历史删除形式,如果有的话。

不过,标签是不同的。删除轻量级标签没什么大不了,但删除一个带注释的标签,这是一个真正的 Git 对象,应该算作删除本地历史。

重写/删除远程仓库历史的操作

据我所知,只有git push -f(相当于git push --force)有可能重写/删除远程存储库中的历史记录。

也就是说,有可能

通过在服务器上设置receive.denyNonFastForwards,禁用将远程分支强制更新为非快进引用的功能。 通过在服务器上设置 receive.denyDeletes 来禁用删除远程存储库上的分支的功能。

此外,我经常使用git reset,但我并不完全意识到我可能对存储库(或其他贡献者的副本)造成的损害。 git reset会有危险吗?

git-reset,正如knittl 所提到的,通常会更改分支参考点的位置。这个命令可能很危险,因为它可以使可访问的提交变得不可访问。因为一张图片说一千个字,考虑以下情况:

你在master 分支上,它指向提交D。现在,假设你跑步,例如,

git reset master~2

软重置被认为是最良性的重置形式,因为它“仅”更改当前分支指向的位置,但不会影响暂存区域或您的工作树。也就是说,仅以这种方式更改分支指向的位置会产生影响:在软重置之后,您最终会得到

在重置之前可以从master 访问的提交CD 现在变得无法访问;换句话说,它们不是任何引用(分支、标签或 HEAD)的祖先。你可以说他们在“repository libo”;它们仍然存在于您的 Git 存储库的对象数据库中,但它们将不再列在 git log 的输出中。

如果您在重置之前确实发现这些提交很有价值,您应该通过引用(例如另一个分支)再次指向提交 D 来使它们再次可访问。否则,当 Git 运行其自动垃圾收集并删除无法访问的对象时,提交 CD 最终将死于真正的死亡

理论上,您可以从reflog 中找出提交D,但始终存在这样的风险,即您会忘记那些无法访问的提交,或者无法识别reflog 的哪个条目对应于哪个条目提交D

总之,是的,git-reset 可能很危险,最好确保您要重置的分支的当前尖端在重置后仍然可以访问。如果需要,在重置之前在那里创建另一个分支,以防万一,作为备份;如果您确定要忘记这些提交,您可以随时删除该分支。

【讨论】:

向那些(就像我一样,几个月前)仍然是 git 新手的用户澄清一下:这个问题与 PUSHING 更改有关到远程存储库。只要提交/分支保持本地git reset --soft/--hard 就没有风险(如果您重置为已推送的提交)。我通常在本地分支上开发新功能,然后将git mergegit cherry-pick 它们放到git push origin 之前的master 分支。只要您留在本地分支机构,您就可以安全地做(我猜)任何事情(并且如前所述,不要触摸已经推送的提交)。 如果有人知道你可以重写历史的任何情况,也在当地分支机构做错事,请写下来! (@Jubobs:无论如何感谢您的完整答案。)【参考方案4】:

根据经验,最危险的命令之一是

git push -f mirror

这会将您的本地 repo 镜像到远程进程中,删除除本地 repo 上的分支之外的所有其他分支。

【讨论】:

以上是关于可能破坏/重写历史的 Git 命令的主要内容,如果未能解决你的问题,请参考以下文章

Git 重写历史

Git 重写历史

重写 Git 历史的实际后果是啥?

修改GIT已提交的用户名和邮箱

如何重写 git 历史以匹配流行的 git 工作流程

git revert和git reset的区别