舍弃本地提交历史记录,并将其替换为原始提交历史记录

时间:2019-02-01 12:59:43

标签: git

让我们假设我的同事和我正在同一个存储库中工作。我们从GitHub存储库中推送和提取提交。这是事件的时间表:

  1. 我和我的同事都在我们的存储库中拥有最新的提交历史记录。
  2. 然后我的同事执行了git commit --amend,以修正master中的最后一次提交。因此,他重写了提交历史。
  3. 如果提交历史记录中有多个提交,而我执行git pull,则它将尝试将其覆盖的提交合并到我现有的提交历史记录中。
  4. 如果提交历史记录中只有一个提交,而我的同事修改了该单独的提交,那么它将创建完全不相关的提交历史,因为最早的提交本身是不同的。如果在这种情况下执行git pull,则会出现以下错误:fatal: refusing to merge unrelated histories

我想做的是丢弃我的本地提交历史记录,并将其替换为GitHub上的提交历史记录。我知道我可以rm -rf .git,但是随后我必须执行多个步骤,例如cd ..rm -rf repogit clone <repo-url>。我正在尝试编写脚本,我正在寻找最简单的方法来使我的存储库与origin保持同步,即使这意味着丢弃我的本地提交历史记录。

是否存在一个git命令,可以轻松地使我们丢弃本地提交历史记录,并用origin的提交历史记录替换它,而不管两个提交历史记录是相关还是不相关?

3 个答案:

答案 0 :(得分:2)

非常简单的命令:

git reset --hard origin/master

答案 1 :(得分:2)

这两个git reset --hard答案是正确的,但可能有助于检查为什么正确以及它们的作用。

请记住,在Git中,提交代表所有文件的完整快照。 (嗯,它包含所有已提交的文件,但是这样说听起来很愚蠢,因为现在我们只是说“一个提交包含该提交包含的文件”。)但是它还具有 metadata ,例如谁进行提交(作者姓名和电子邮件),何时以及为什么(日志消息)。每个提交都由一些大的,难看的,显然是随机的哈希ID唯一标识。此外,每个提交中的元数据项之一是提交的 parent 的哈希ID。这些字符串在向后看的链中一起提交:

... <-F <-G <-H

“提交历史记录”只是一个提交,然后是其父提交,然后是下一个父提交,依此类推,它们都是按照从父到父的提交哈希ID链反向完成的。我们从结尾开始-在这种情况下,从哈希H提交。这样就可以获取快照,还可以获取提交G的ID。然后,我们移到G,它获得了F的ID,这使我们可以继续向后移动。 (最终,我们与父代( no )(该存储库中的第一个父代)进行了提交,这使我们停止了操作。)

但是:我们如何首先知道从H开始?这是分支名称的来源。我们有一个类似master的名称,在这个名称中,我们存储了 last 提交的哈希ID。案例H

...--F--G--H   <-- master (HEAD)

运行git fetch时,您的Git从其他Git获得提交。您的姓名masterdevelop等继续记录现有提交的哈希ID。您的远程跟踪名称-origin/masterorigin/develop等-记录提交的哈希ID。

尽管哈希ID看起来是随机的,但它们不是随机的!实际上,它们是通过对每个提交的所有内容(包括其父提交哈希)进行校验和来计算的。因此,如果我们的Git及其Git从相同提交开始 (例如,我们最初克隆了他们的提交),我们将共享 some 一组提交哈希:

A--B--...--H   <-- origin/master
            \
             I--J    <-- master (HEAD)

当我们提取他们的 new 提交时,它们的旧提交(A-B-...-H)仍然存在,我们最终得到了一些以前没有的内容:

             K--L   <-- origin/master
            /
A--B--...--H
            \
             I--J    <-- master (HEAD)

现在我们必须做一些事情来将我们的提交与他们的提交结合起来。

但是,如果我们不喜欢我们的提交,我们可以将我们的提交扔掉并使用它们。也就是说,我们可以告诉Git:停止查看提交J,开始查看提交L我们让Git粗暴地拉我们 our master,使其指向L,而忘记了我们甚至曾经{em>曾经 I-J链:

             K--L   <-- master (HEAD), origin/master
            /
A--B--...--H
            \
             I--J    [abandoned]

我们通过git reset进行这种调整。

对于这种特殊情况(我们要清除索引和工作树并从要跳转到的提交中替换它们),我们添加--hard

Git使用附加到我们当前分支HEAD的特殊名称master来知道要提取哪个分支名称。在此示例中,只有一个分支名称,这很明显,但是当我们添加更多分支名称时,git reset会影响附加了HEAD的分支名称。

请注意,当我们添加 new 提交时,Git会执行相同的操作。新提交的父对象是HEAD所指向的任何提交,或更准确地说,是HEAD所指向的名称所指向的。然后,HEAD所附加的名称,例如master moves! Git将其移至我们刚刚进行的新提交,这就是最后一次提交,我们将向后移动一次,一次提交,以查找历史记录。

如果您和其他人都从完全空白的存储库开始,并且进行了提交,则会得到:

A   <-- master (HEAD)

他们得到了一些其他的哈希ID:

B   <-- master (HEAD)

当您从他们那里git fetch时,您也会得到他们的 提交,并且您将名称origin/master设置为记住他们的master,所以现在您有了:

A   <-- master (HEAD)

B   <-- origin/master

如果您现在git reset --hard origin/master,Git会重新指向当前分支名称(master,附加了HEAD),以便它指向提交B,您从他们那里得到了origin/master指向的对象,因为origin/master记得当您的Git调用他们的Git并要求他们提交最新内容时,他们的Git告诉了您的Git。所以现在您将拥有:

A   [abandoned]

B   <-- master (HEAD), origin/master

如果您想保留您的A,只需在覆盖当前分支之前设置一个名称以记住该名称。也就是说,尽管您仍然记得A的实际哈希ID,但请执行以下操作:

git branch save <hash-ID>

或者,只要master仍然指向A(您尚未完成git reset):

git branch save master

然后您将拥有:

A   <-- master (HEAD), save

B   <-- origin/master

然后在git reset --hard origin/master之后,您将拥有:

A   <-- save

B   <-- master (HEAD), origin/master

这里的规则非常简单:

  • 提交永不更改。制作完成后,它们将永久冻结。

  • 但是我们(人类)记住并找到了名称的提交,以及分支名称中的名称到哈希ID的映射,例如{{1} } 进行更改。

  • 当我们进行新的提交时,Git自动更新当前分支名称(基于master的附件)。

  • HEAD以任意方式更改当前分支名称。

如果您为提交丢失了 only 名称,则不在其他名称的历史记录中(因此您无法通过向后跟踪过程找到它),Git最终(通常在30天左右的某个时间后)会丢弃提交本身。因此,如我们通过git reset那样放弃一个提交或一连串的提交,最终可能会冻结冻结的提交。 (因此,如果要保存/保存它们,请确保在链中的 last 提交中使用一些名称。)

这意味着一直冻结,在第一个要点有点夸张:只要提交本身存在,就保持 。但是,就像说“提交快照保存提交快照所保存的文件”一样,说这种话似乎有些言过其实。说永远冻结一次提交并不是100%正确,但是已经足够接近:只要提交本身继续存在,它就会被冻结。

提交(由哈希ID标识)是冻结的,静态的,不变的;诸如git reset之类的分支名称不断变化并且不断变化。您可以根据需要移动分支名称,以使冻结的提交可查找。只要需要创建更多历史记录,就添加 new 提交。当您想忘记一些历史记录时,您可以将它们从现在的位置粗暴地拖到其他地方。

哈希ID是通用的:它们在每个 Git存储库中是永远相同的(嗯,曾经与 this Git存储库共享的每个Git存储库)。 分支名称特定于此特定的Git存储库!

答案 2 :(得分:1)

您要做的最好选择是使用git reset --hard origin/masterHEAD设置为origin/master