【问题标题】:How to sync local history after massive git history rewrite?大规模git历史重写后如何同步本地历史?
【发布时间】:2018-01-15 16:28:31
【问题描述】:

这个问题可能看起来很奇怪,但我在重写 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 reset --hard HEAD 仅刷新您的 current 提交中的文件,如果这是在重写期间丢失的提交之一,这将无济于事。而是使用git reset --hard origin/branchname,替换为您所在的分支名称(如果需要,请替换为您的遥控器的名称)。另外,请确保您有本地存储库的备份,如果您丢失文件,我概不负责。
  • @LasseVågsætherKarlsen 我是否必须单独重置每个分支(或通过循环脚本)?没有像 fetch 这样的 Git 命令来刷新所有分支的完整历史记录?

标签: git git-rewrite-history


【解决方案1】:

理解这里的问题的关键是(是)在 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 写入的 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',给出:

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 克隆可能不会比更新现有的更昂贵。这当然更容易!

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

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

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

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

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

【讨论】:

  • 我以为我对 Git 有所了解。感谢您使用 ID 和父级内容升级我的 Git 知识。对此点赞!
  • 对最后一句话感到难过。我希望有一个像 fetch 这样的 Git 命令可以一次重置所有分支。
【解决方案2】:

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

【讨论】:

    猜你喜欢
    • 2013-04-15
    • 1970-01-01
    • 2021-12-09
    • 2021-05-29
    • 2012-12-01
    • 1970-01-01
    • 2014-09-25
    • 2015-07-07
    • 2020-05-30
    相关资源
    最近更新 更多