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

How to sync local history after massive git history rewrite?

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

在我重写的 machine 上,一个简单的 git fetch 同步了所有内容。

在另一个 mac machine 上,git sync 没有帮助,但是在随机删除本地 .git/ 日志和 refs 文件然后发出 git pull, 历史更新了。

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

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

我使用本教程进行了大量历史重写:

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 fetch,而不是 git pull。然后对于每个历史被重写的分支,你需要做git reset --hard HEAD。请注意,此命令仅适用于当前分支。因此,如果不止一个分支受到历史重写的影响,您需要检查并重置每个分支。

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

  • 提交历史。
  • 任何提交的 "true name" 是其哈希 ID。
  • 不能永远更改任何提交。
  • 每个提交通过哈希 ID 记住其先前的(直接祖先,又名 parent)提交。
  • 名称,包括分支和标签名称,主要只存储一 (1) 个哈希 ID。
  • 分支名称的特殊属性是随着分支的增长改变它存储的哈希ID ,通常以 "nice" 方式,以便无论今天提交分支名称,该提交(通过哈希 ID)最终都会返回到昨天标识的名称的提交(通过哈希 ID)。

当您 "rewrite history" 时,您 不能 不更改任何现有提交。相反,您 复制 每个现有的提交。 git filter-branch 所做的是复制您请求的所有提交,按照 "oldest"(最古老的)到 "newest"(最不古老的/最尖端的)顺序,应用过滤器:

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

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

如果你有一个只有三个提交的小型存储库——我们称它们为提交 AC——和一个 master 分支,并且所有三个提交都需要一些更改), 你会得到这个:

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

A'-B'-C'  <-- master

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

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

A--B--C
 \
  B'-C'  <-- master

作为最终结果。

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

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

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

A--B--C
    \
     C'  <-- master

处理上游重写

一般来说,the easiest way for people to deal with a really massive upstream origin rewrite is for them to discard their existing repositories entirely。也就是说,我们希望共享不超过几个原始提交:在大规模重写的某个早期点,我们更改提交 A 或附近的一个,以便每个后续提交都必须复制到新提交。因此,创建一个 new 克隆可能并不比更新现有克隆贵多少。这肯定 更容易!

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

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

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

使我们的 branch 名称,作为其提示提交,与 origin/<em>branch[=128] 相同的提交=] 个名字。 (请记住,git fetch 强制更新所有 our origin/<em>branch</em> 名称以匹配branchorigin 上指向的哈希 ID。)

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