如何在大规模 git 历史记录重写后同步本地历史记录



这个问题可能看起来很奇怪,但是在重写了 100 多次提交后,我在同步 git 历史记录时遇到了问题。

在我重写的机器上,一个简单的git fetch同步了这一切。

在另一台Mac机器上,git sync没有帮助,但是在随机删除本地.git/日志和refs文件然后发出git pull后,历史记录被刷新了。

但是,无论我在 Windows 机器上做什么,我都无法刷新项目历史记录。都试过了:

  • git reset --hard HEADgit fetch
  • git fetch --all
  • git pull

每次在 Windows 机器上,我都会收到不同作者的同一提交的重复条目(我更改了作者字段)。

我使用本教程进行了大规模的历史重写:

https://help.github.com/articles/changing-author-info/

Open Terminal.
Create a fresh, bare clone of your repository:
git clone --bare https://github.com/user/repo.git
cd repo.git
Copy and paste the script, replacing the following variables based on the information you gathered:
OLD_EMAIL
CORRECT_NAME
CORRECT_EMAIL
#!/bin/sh
git filter-branch --env-filter '
OLD_EMAIL="your-old-email@example.com"
CORRECT_NAME="Your Correct Name"
CORRECT_EMAIL="your-correct-email@example.com"
if [ "$GIT_COMMITTER_EMAIL" = "$OLD_EMAIL" ]
then
export GIT_COMMITTER_NAME="$CORRECT_NAME"
export GIT_COMMITTER_EMAIL="$CORRECT_EMAIL"
fi
if [ "$GIT_AUTHOR_EMAIL" = "$OLD_EMAIL" ]
then
export GIT_AUTHOR_NAME="$CORRECT_NAME"
export GIT_AUTHOR_EMAIL="$CORRECT_EMAIL"
fi
' --tag-name-filter cat -- --branches --tags
view rawgit-author-rewrite.sh hosted with ❤ by GitHub
Press Enter to run the script.
Review the new Git history for errors.
Push the corrected history to GitHub:
git push --force --tags origin 'refs/heads/*'
Clean up the temporary clone:
cd ..
rm -rf repo.git

有没有人经历过大规模的 git 历史重写?如果是,其他团队成员刷新其 git 历史记录的步骤是什么?

理解这里问题的关键(或关键)是(是)在 Git 中:

  • 提交历史记录。
  • 任何提交的"真实名称"都是其哈希ID。
  • 任何提交都无法更改。
  • 每个提交都会通过哈希 ID 记住其以前的(直接祖先,也称为级)提交。
  • 名称,
  • 包括分支和标签名称,主要只存储一 (1) 个哈希 ID。
  • 分支名称
  • 的特殊属性是,随着分支的增长,它通常会以"好"的方式更改它存储的哈希 ID,以便无论今天提交分支名称,该提交(通过哈希 ID)最终都会导致返回名称昨天标识的提交(通过哈希 ID)。

当你"重写历史"时,你不会——你不能——改变任何现有的提交。 相反,您可以复制每个现有提交。git filter-branch所做的是将您请求的所有提交按"最旧"(最祖先)到"最新"(最不祖先/最尖端)的顺序复制,并按原样应用过滤器:

  • 提取原始提交;
  • 应用过滤器;
  • 从结果进行新提交,父哈希 ID 更改由任何以前的一个或多个副本决定。

最后,对于真正大规模的重写来说,这意味着您本质上有两个不同的存储库并排放置:旧的存储库及其旧提交,以及新的存储库及其新提交。 在筛选过程结束时,git filter-branch更改名称以指向新副本。

如果你有一个只有三个提交的小型存储库(我们称之为AC提交)和一个master分支,并且所有三个提交都需要一些更改,您将拥有以下内容:

A--B--C   [was the original master]
A'-B'-C'  <-- master

从字面上看,新提交是提交。 任何仍在使用旧提交的人实际上仍在使用旧提交。 他们必须停止使用这些提交,而是开始使用新的提交。

在某些情况下,您指定的过滤器最终不会git filter-branch原始提交中的任何内容。 在这种情况下,如果filter-branch写入的新提交与原始提交逐位相同,则只有这样,新提交实际上与旧提交相同。 如果我们查看相同的三次提交原始存储库,但选择一个过滤器,仅修改第二个提交B的内容或元数据,我们会得到:

A--B--C

B'-C'  <-- master

作为最终结果。

请注意,即使过滤未更改原始C,也会发生这种情况。 这是因为原始B的某些内容更改,导致新的和不同的提交B'。 因此,当git filter-branch复制C时,它必须进行一个更改:复制C'的父级是新B'而不是原始B

也就是说,git filter-branchA复制到新的提交中,但根本没有进行任何更改(甚至没有对任何父信息进行任何更改),因此新提交原来是对原始A的重用。 然后它B复制到一个新的提交中,并进行了更改,所以新的提交现在B'。 然后它复制C而不进行更改,将父级更改为B',并编写新的提交C'

如果您的过滤器仅对C进行了更改,则git filter-branch命令会将A复制到自身,B复制到自身,C复制到C',给出:

A--B--C

C'  <-- master

处理上游重写

一般来说,人们处理非常大规模的上游origin重写的最简单方法是完全丢弃现有的存储库。 也就是说,我们希望共享的原始提交不超过几个:在大规模重写的某个早期点,我们将提交A或附近的提交更改为它附近的提交,以便每个后续提交都必须复制到新的提交中。 因此,创建新克隆可能并不比更新现有克隆贵多少(如果有的话)。 这当然更容易!

严格来说,这不是必要的。 作为"下游"使用者,我们可以运行git fetch并获取所有新提交及其更新的分支名称,也许还有更新的标签(这里要特别小心,因为标签默认情况下不会更新)。 但是由于我们有自己的分支名称,指向原始提交而不是新复制的提交,我们现在必须使我们的每个分支名称都引用新复制的提交,也许还复制我们拥有的任何上游没有的提交(因此尚未复制)。

换句话说,我们可以为每个分支运行:

git checkout <branch>
git reset --hard origin/<branch>

使我们的branch名称(作为其提示提交)与origin/branch名称相同的提交。 (请记住,git fetch强制更新我们所有的origin/branch名称,以匹配originbranch指向的哈希 ID。

这相当于删除我们的每个分支并使用git checkout重新创建它们。 换句话说,它不会继承我们任何重写origin没有复制的提交(因为他们不能,因为他们没有)。 为了推进我们的提交,我们必须做与处理上游变基相同的事情。 内置的分叉点代码是否会为你正确地做到这一点——如果你的 Git 至少是 2.0,它通常会这样做——实际上是一个单独的问题(并且已经在其他地方得到了回答)。请注意,您必须对要结转的每个分支执行此操作。

在第二台机器上,首先运行git fetch,而不是git pull。然后对于历史记录被重写的每个分支,您需要执行git reset --hard HEAD.请注意,此命令仅适用于当前分支。因此,如果多个分支受到历史记录重写的影响,则需要签出并重置每个分支。

最新更新