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

10

能否提供一个(所有或最常见的)可能会危及 git 历史记录的操作或命令列表?

什么是绝对应该避免的?

  1. 在推送之后修改提交(git commit/git push/git commit --amend
  2. 向已经推送的内容进行变基(rebase)

如果此问题尚未在其他地方提出过,我希望它成为有关 git 常见可避免操作的参考

此外,我经常使用 git reset,但并不完全了解可能会对仓库(或其他贡献者的副本)造成的潜在破坏。 git reset 是否存在风险?

4个回答

16

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

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

请查看commit 483e861, commit 9df53c5, commit 7b6ad97 (2019年9月4日)由Elijah Newren (newren)提交的内容。
(于2019年9月30日由Junio C Hamano -- gitster --合并至commit 91243b0)

建议使用git-filter-repo代替git-filter-branch

filter-branch存在许多伪装的危险,这些危险会使历史重写出现偏差。很多问题不显眼,并且很容易在新仓库中发现,从更混乱的历史记录到数据丢失或损坏等问题。这些问题无法向后兼容地解决,因此应向filter-branch及其手册添加警告,推荐使用其他工具(如filter-repo)。

同时,更新引用filter-branch的其他手册。其中一些需要更新,即使我们可以继续推荐filter-branch,因为它们暗示某些内容是特定于filter-branch,而实际上适用于所有历史重写工具(例如BFGreposurgeonfast-importfilter-repo),或者因为某些关于filter-branch的内容被用作示例,尽管现在已经有其他更常见的示例存在。

最后,删除解释BFG Repo Cleaner作为filter-branch替代方案的部分。虽然我对此感到有些内疚,特别是因为我觉得我从BFG中学到了很多东西,并将其运用到filter-repo中(这比我能说出的关于filter-branch的多得多),但保留该部分会出现一些问题:

  • 为了建议人们停止使用filter-branch,我们需要向他们提供另一个可以处理所有相同重写类型的工具建议。据我所知,filter-repo是唯一的这样的工具。因此,需要提到它。
  • 我们不想向用户提供冲突的建议。
  • 如果我们推荐两个工具,我们不应该期望用户学习并选择使用哪一个;我们应该解释哪些问题一个工具可以解决而另一个不能,或者何时一个工具比另一个快得多。
  • BFGfilter-repo的性能类似。
  • BFG可以执行的所有过滤类型,filter-repo也可以执行。实际上,filter-repo带有一个名为bfg-ish的重新实现BFG,提供与BFG相同的用户界面,但具有几个难以在BFG中实现的错误修复和新功能。
虽然我仍然可以提到两个工具,但似乎我需要提供某种比较,并最终只会说filter-repo可以做到BFG所能做的一切,因此最终似乎最好完全删除该部分。
有哪些可以危及Git历史记录的操作或命令?
至少,newren/git-filter-repo 可以从其使用中危及的任何历史记录中恢复。
在它所声明的目标中:
更智能的安全性 将原始 refs 的副本写入存储库内的特殊命名空间并不提供用户友好的恢复机制。许多人会因此而感到困惑。 几乎我见过的每个进行存储库过滤操作的人都是用新克隆的方式进行的,因为如果出现错误,清除克隆是一种非常简单的恢复机制。 通过检测并退出,强烈鼓励这种工作流程,除非用户使用 --force 覆盖。

git filter-repo文档中提到的所述,大致通过运行以下命令来工作:

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

git fast-export / git fast-import 在 git 2.24 (2019年第四季度) 中有一些改进。

请参见commit 941790dcommit 8d7d33ccommit a1638cfcommit 208d692commit b8f50e5commit f73b2abcommit 3164e6b(2019年10月3日)和commit af2abd8(2019年9月25日),作者是Elijah Newren (newren)
(由Junio C Hamano -- gitster --合并于commit 16d9d71,2019年10月15日) 例如:

fast-import:允许通过标记标签来识别标签

签名:Elijah Newren

fast-exportfast-import中,标记标识符用于提供一个标签以引用先前的内容。

由于需要在第一次出现给定文件名的提交中引用Blob,因此为Blob赋予标签,并且由于提交可以是其他提交的父项,因此为提交赋予标签。

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

  1. 如果我们想创建标记的标记(或更高嵌套),则没有办法引用先前的标记。
  2. 当使用--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
@J.M.Janzen 奇怪,我没有收到那个(但我使用的是Git 2.23) - VonC

8

以下是我记得的:

  • git commit --amend 可以重写上一个提交
  • git rebase 可以重写多个提交(使用 git pull 命令时加上 --rebase 标志或在 branch.$name.rebase 配置选项中也会执行 rebase 操作)
  • git filter-branch 可以重写多个提交
  • git push -f 可以更改分支指向的提交(git push origin +branch 语法也是一样的)
  • git reset 可以更改分支指向的提交
  • git branch -f 可以更改分支指向的提交(通过使用相同名称重新创建分支)
  • git checkout -B 可以更改分支指向的提交(通过使用相同名称重新创建分支)

git pull --rebase,也是。 - VonC
嗯,不错的列表。有很多命令我应该更好地学习一下。你能否至少深入解释一下 git reset 的情况?任何 git reset(带/不带 --soft--hard)都可能会产生风险吗?可以举个实际例子吗? - Kamafeather
@Kamafeather: git reset $commit会更改当前分支的尖端,无论使用哪个标志(soft/hard/mixed/keep/merge/…)。例如:git reset HEAD^; git push -f——这将从发布历史中删除最后一次提交(在服务器上的结果与git push -f origin HEAD^:HEAD无法区分)。 - knittl
1
@Kamafeather git svn dcommit 还会更改历史记录中尚未提交到 Subversion 存储库的每个提交。 - FSMaxB

6

knittl 已经整理了一份关于重写 Git 历史的命令列表,但我想在此基础上进一步补充。

你能提供一份操作或命令列表,说明哪些操作可能会破坏 Git 历史吗?绝对应该避免哪些操作?

首先,就本身而言,重写/删除历史并没有什么问题;毕竟,你可能经常创建功能分支,将其严格保留在本地,然后在不加思考地删除(在合并它们或意识到它们没有用处后)。

然而,当你在本地重写/删除其他人已经访问过的历史记录,并将其推送到共享的远程库时,你肯定会遇到问题。

应视为重写/删除本地库历史的操作

当然,有些愚蠢的方式可能会破坏或删除历史记录(例如篡改 .git/objects/ 的内容),但这些超出了我的回答范围。

您可以以多种方式重写本地存储库的历史记录。《Pro Git》书中重写历史记录章节提到了一些方法:

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

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

  • 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是否会有危险?
knittl所述,git-reset通常会更改分支引用的指向位置。这个命令可能很危险,因为它可能会使可达提交变得不可达。因为一张图片胜过千言万语,考虑下面的情况:

enter image description here

你现在位于指向提交 Dmaster 分支。假设你运行以下命令:
git reset master~2

软重置被认为是最温和的重置形式,因为它“仅”改变当前分支指向的位置,但不影响暂存区或工作树。尽管如此,在该方式下仅改变分支指向位置也会产生影响:在软重置之后,你将得到

enter image description here

在重置之前从主分支master可达的提交CD现在已经变得不可达了;换句话说,它们不是任何引用(分支、标签或HEAD)的祖先。你可以说它们处于"存储库地狱"中;它们仍然存在于Git仓库的对象数据库中,但将不再列在git log的输出中。

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

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

总之,是的,git-reset可能会很危险,因此最好确保在重置之后,你要重置的分支的当前末尾仍然可以到达。如果需要,在重置之前在那里创建另一个分支作为备份;如果你确定要忘记这些提交,随时可以稍后删除该分支。


为了澄清对于那些(像我几个月前一样)仍然是_git_新手的用户,这个问题与将更改PUSHING到远程存储库有关。只要提交/分支保持本地,使用git reset --soft/--hard(如果您重置已经推送的提交)就没有风险。我通常在本地分支上开发新功能,然后在git push origin之前将它们合并到_master_分支中,或者使用git cherry-pick命令。只要您留在本地分支上(并且不触及已经推送的提交),您可以安全地进行任何操作(我猜)。 - Kamafeather
如果有人知道任何情况,可以在本地分支上重写历史并做错事,请写下来!(@Jubobs:无论如何感谢您的完整答案。) - Kamafeather

2

从经验来看,最危险的命令之一是

git push -f mirror

这会将您的本地repo镜像到远程服务器,同时删除所有除本地repo中以外的其他分支。

"Original Answer"翻译为"最初的回答"。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接