这个问题可能看起来很奇怪,但是在重写了 100 多次提交后,我在同步 git 历史记录时遇到了问题。
在我重写的机器上,一个简单的git fetch
同步了这一切。
在另一台Mac机器上,git sync
没有帮助,但是在随机删除本地.git/
日志和refs文件然后发出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 历史记录的步骤是什么?
理解这里问题的关键(或关键)是(是)在 Git 中:
- 提交是历史记录。
- 任何提交的"真实名称"都是其哈希ID。
- 任何提交都无法更改。
- 每个提交都会通过哈希 ID 记住其以前的(直接祖先,也称为父级)提交。 名称,
- 包括分支和标签名称,主要只存储一 (1) 个哈希 ID。 分支名称
- 的特殊属性是,随着分支的增长,它通常会以"好"的方式更改它存储的哈希 ID,以便无论今天提交分支名称,该提交(通过哈希 ID)最终都会导致返回名称昨天标识的提交(通过哈希 ID)。
当你"重写历史"时,你不会——你不能——改变任何现有的提交。 相反,您可以复制每个现有提交。git filter-branch
所做的是将您请求的所有提交按"最旧"(最祖先)到"最新"(最不祖先/最尖端)的顺序复制,并按原样应用过滤器:
- 提取原始提交;
- 应用过滤器;
- 从结果进行新提交,父哈希 ID 更改由任何以前的一个或多个副本决定。
最后,对于真正大规模的重写来说,这意味着您本质上有两个不同的存储库并排放置:旧的存储库及其旧提交,以及新的存储库及其新提交。 在筛选过程结束时,git filter-branch
更改名称以指向新副本。
如果你有一个只有三个提交的小型存储库(我们称之为A
C
提交)和一个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-branch
A
复制到新的提交中,但根本没有进行任何更改(甚至没有对任何父信息进行任何更改),因此新提交原来是对原始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
名称,以匹配origin
branch
指向的哈希 ID。
这相当于删除我们的每个分支并使用git checkout
重新创建它们。 换句话说,它不会继承我们任何重写origin
没有复制的提交(因为他们不能,因为他们没有)。 为了推进我们的提交,我们必须做与处理上游变基相同的事情。 内置的分叉点代码是否会为你正确地做到这一点——如果你的 Git 至少是 2.0,它通常会这样做——实际上是一个单独的问题(并且已经在其他地方得到了回答)。请注意,您必须对要结转的每个分支执行此操作。
在第二台机器上,首先运行git fetch
,而不是git pull
。然后对于历史记录被重写的每个分支,您需要执行git reset --hard HEAD
.请注意,此命令仅适用于当前分支。因此,如果多个分支受到历史记录重写的影响,则需要签出并重置每个分支。