在GitHub上更改作者信息后应执行哪些本地操作?

时间:2019-04-21 02:00:28

标签: git rebase

我想从提交给GitHub存储库的提交中删除个人电子邮件地址,所以我遵循了Git Bash steps they provide,它在临时克隆存储库中使用git filter-branch更新了受影响的提交,并通过运行结束这个:

git push --force --tags origin 'refs/heads/*'

现在,回购在GitHub上看起来是正确的,其中包含已清理的电子邮件。但是,我是Git的新手,不确定要同步本地副本需要做哪些后续工作。

当我尝试拉时,出现错误“拒绝合并无关的历史记录”。

在此之前,我在本地没有任何更改,所以也许最简单的事情就是删除我的本地存储库,然后再次签出项目,但这并不是我所了解的最佳实践或最灵活的方法。

似乎我需要根据改写的历史为基础,也许像这样:

git pull --rebase

那是最好的方法吗?如果没有,那是什么?

侧面注意:我正在IntelliJ IDEA中工作,理想情况下,仅将cmd行用于诸如作者更改脚本之类的不寻常事件,并且其Pull对话框没有Rebase选项,而Update Project却具有Rebase选项,因此我确实做到了是吗?

2 个答案:

答案 0 :(得分:2)

当您这样重写历史记录时,您以及您的案例都可以得到相当于新的不同存储库的内容。在这种情况下,旧存储库的所有现有克隆都只能与旧存储库一起使用。现在,您只需简单地创建新存储库的新克隆,这是一个永远都不应连接到旧项目的新项目:两者不再兼容,并且提交不再可以从一个转移到另一个。

这是复杂现实的简化视图,但足以满足您的情况。如果您想了解现实,请继续阅读。

Git主要在乎提交,但是提交到底是什么?

Git存储库的本质是一对数据库。大数据库是一个包含所有提交(或更确切地说,所有Git对象)的数据库。 (Git对象有四种类型:commit,tree,blob和带注释的标记。树和blob是提交如何在其内部存储文件的方式,而带注释的标记对象仅用于保存带注释的标记数据。)每个唯一的Git对象都有一个唯一的哈希ID,因此每个提交都有自己的唯一哈希ID,与其他所有提交都不相同。

所有这些哈希ID不仅都是唯一的,而且都是通用。 (它们是Globally Universal IDs or GUIDs, also called UUIDs。)这意味着宇宙中每个地方的每个Git都使用相同 GUID进行提交。

Git实际实现此目的的方式是ID是提交的 content 的加密校验和。这意味着在提交中更改任何内容几乎是不可能的:如果您实际上设法更改了某些内容,那么您得到的是一个新的且不同的提交,具有一个新的且不同的哈希ID。给定哈希ID,Git可以检查它是否具有对象。如果是这样,它可以检索该对象。如果没有,您的Git可以向其他一些具有该对象的Git询问完整的对象,然后将生成的对象填充到其大数据库中。

只要有哈希ID并且实际对象在数据库中,我们就说我们有一个指向该对象的指针。这些指针使我们能够找到提交(或其他Git对象,但大多数情况下我们使用提交)。

在任何情况下,提交的实际内容通常都很短:每个提交都保存该提交的文件快照的哈希ID(这是您要永久保存的数据)以及一组元数据,例如您的姓名和电子邮件地址。但是,每个提交的元数据片段之一就是提交的 parent 哈希ID(如果提交是合并提交,则为复数ID)。因此,每个提交都通过哈希ID指向其父对象。

我们可以绘制它,如果我们使用单个大写字母来代表提交,它甚至看起来是合理的。 (当然,我们很快就会用完所有字母,这就是Git使用那些大的丑陋哈希ID的原因。)这是一个示例,该存储库仅包含master和八个提交,其哈希ID为A通过H

A <-B <-C ... <-F <-G <-H   <--master

名为master的分支上的 last 提交具有哈希ID H。提交H本身存储提交G的哈希ID,该哈希ID存储F的ID,依此类推。最终,我们一直工作到有史以来的第一次提交,即提交A。它没有父对象,因为它没有父对象:这是第一次提交。这让我们(和Git)停了下来。

请注意,Git必须一直向后向后工作。我们总是从某个分支名称的结尾处开始-某个分支的 tip commit 。因此,Git的第二个较小的数据库是一个名称表-分支名称,标记名称和其他引用-每个表都只包含一个哈希ID。当引用是分支名称时,哈希ID是提交的哈希ID,并通过跟随所有向后指向的箭头,我们可以找到从分支 可以到达的所有提交。

当我们创建一个新分支时,我们只是创建一个新的名称,它指向一些现有的提交:

...--F--G--H   <-- master, develop

现在两个分支都指向提交H。我们选择一个分支为“ on”,然后使用git checkoutHEAD附加到分支:

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

现在,我们可以按照通常的方式进行新的提交了。完成后,Git打包所有文件,附加我们的元数据(我们的日志消息,名称,电子邮件地址,时间戳等),并写出新的提交。新提交的父级是当前提交H。新提交的数据散列到一个看起来很丑陋,看起来很随机的字符串,该字符串不同于其他任何一次提交,但是我们只调用I

...--F--G--H   <-- master, develop (HEAD)
            \
             I

现在真正聪明的事情发生了。现在,Git将I的哈希ID写入附属于以下分支名称的HEAD

...--F--G--H   <-- master
            \
             I   <-- develop (HEAD)

如果我们切换回master并在那里进行新的提交,则两个分支会分开。

过滤器分支副本提交

git filter-branch的作用是列出每个提交(或某个子集中的每个提交,具体取决于您的选择),并开始提取每个提交,运行您指定的过滤器,再加上一个,尽管您可以指定也是一个,并从结果中进行新的提交。根据定义,每当过滤器进行任何更改时,新提交都不会与旧提交完全相同,因此它将获得不同的哈希ID。 1 额外的过滤器是进行新提交的过滤器,它会自动将父哈希ID换为进行任何较早更改的结果。因此,假设您有:

        D--E   <-- master
       /
A--B--C
       \
        F--G  <-- feature

,过滤器将更改您的作者信息。提交A成为新的提交A'

        D--E   <-- master
       /
A--B--C
       \
        F--G  <-- feature

A'   [in progress]

现在,过滤器分支必须复制B。即使您的过滤器进行了 no 更改,新提交也必须以A'作为父提交,而不是A,因此最终的提交制作者会更改父哈希(也许是早期的过滤器也会更改作者信息),我们得到:

        D--E   <-- master
       /
A--B--C
       \
        F--G  <-- feature

A'-B'   [in progress]

这一直重复到EG

        D--E   <-- master
       /
A--B--C
       \
        F--G  <-- feature

        D'-E'  <-- (replacement for master)
       /
A'-B'-C'
       \
        F'-G'  <-- (replacement for feature)

git filter-branch在每次提交后都经过一次传递,它将替换名称:将E'的ID填充到master中,并将G'的ID填充到{ {1}},现在您的姓名数据库不再完全记住原始的featureE,并且您查看的所有内容都将从GE'开始。这些新的(真的或至少应该是经过改进的)提交是您想要的。您想要忘记旧的。

旧的提交 仍在其中-实际上,例如,过滤器分支将原始的G'引用复制到master-但是新的一组提交是新的存储库。克隆此存储库将复制原始文件,仅复制可访问的新的和改进的提交。删除refs/original/refs/heads/master名称将最终使Git垃圾收集旧的提交(通常在30天后的某个时间,尽管确切的时间长短取决于许多其他因素)。


1 如果过滤器在字面上没有做 no 更改,则新提交 与原始提交完全相同,因此具有原始提交的哈希ID,从字面上看 就是原始提交。但是最后一个过滤器(使它自己进行提交的过滤器)通常会更改某些内容。

答案 1 :(得分:0)

无论您选择使本地存储库同步的过程如何,最终结果都是相同的:两个存储库都具有相同的历史记录。在这种情况下,本地版本将被远程版本覆盖。

因此,由于您没有本地更改要保留,因此很可能没有比您认为的再次克隆项目更干净,更快捷的事情了。我会说的去做。

(作为旁注,我看不出这是不好的做法的任何原因。您预见到什么具体问题?)