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

时间:2018-01-15 16:28:32

标签: git git-rewrite-history

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

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

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

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

  • git reset --hard HEAD& git 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历史的步骤是什么?

2 个答案:

答案 0 :(得分:4)

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

  • 提交 历史。
  • "真名"任何提交都是它的哈希ID。
  • 永远不会更改
  • 每个提交都会通过哈希ID记住其先前的(直接祖先,即)提交。
  • 名称,包括分支和标记名称,主要只存储一(1)个哈希ID。
  • 分支名称的特殊属性是更改它存储的哈希ID ,随着分支的增长,通常在" nice"因此,无论今天提交分支名称的是什么,该提交(通过哈希ID)最终都会返回到昨天识别的名称的提交(通过哈希ID)。

当你"重写历史记录"时,你没有 - 你可以不改变任何现有的提交。相反,您复制每个现有提交。 git filter-branch做的是复制您请求的所有提交,最早的" (最祖先的)到#34;最新的" (最少祖先/最尖端)订单,按原样应用过滤器:

  • 提取原始提交;
  • 应用过滤器;
  • 从结果中进行新提交,其中父哈希ID更改由任何先前的副本指定。

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

如果你有一个只有三次提交的小型存储库 - 让我们调用它们AC - 和一个master分支,并且所有三个提交都需要进行一些更改(s),你会有这个:

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

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

新提交实际上是 new 提交。仍然使用旧提交的任何人仍然使用旧提交。他们必须停止使用这些提交并开始使用新的提交。

在某些情况下,您使用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-branchA复制到新的提交,但根本没有任何更改(甚至没有任何父信息),因此新的提交结果是重新使用原A。然后它将B复制到新的提交,并进行了更改,因此新提交现在为B'。然后,它复制C而不进行更改,将父级更改为B',并编写新的提交C'

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

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 克隆可能并不比更新现有克隆更昂贵。它肯定更容易!

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

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

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

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

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

答案 1 :(得分:0)

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