我想从提交给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选项,因此我确实做到了是吗?
答案 0 :(得分:2)
当您这样重写历史记录时,您以及您的案例都可以得到相当于新的不同存储库的内容。在这种情况下,旧存储库的所有现有克隆都只能与旧存储库一起使用。现在,您只需简单地创建新存储库的新克隆,这是一个永远都不应连接到旧项目的新项目:两者不再兼容,并且提交不再可以从一个转移到另一个。
这是复杂现实的简化视图,但足以满足您的情况。如果您想了解现实,请继续阅读。
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 checkout
将HEAD
附加到分支:
...--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]
这一直重复到E
和G
:
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}},现在您的姓名数据库不再完全记住原始的feature
和E
,并且您查看的所有内容都将从G
或E'
开始。这些新的(真的或至少应该是经过改进的)提交是您想要的。您想要忘记旧的。
旧的提交 仍在其中-实际上,例如,过滤器分支将原始的G'
引用复制到master
-但是新的一组提交是新的存储库。克隆此存储库将不复制原始文件,仅复制可访问的新的和改进的提交。删除refs/original/refs/heads/master
名称将最终使Git垃圾收集旧的提交(通常在30天后的某个时间,尽管确切的时间长短取决于许多其他因素)。
1 如果过滤器在字面上没有做 no 更改,则新提交 与原始提交完全相同,因此具有原始提交的哈希ID,从字面上看 就是原始提交。但是最后一个过滤器(使它自己进行提交的过滤器)通常会更改某些内容。
答案 1 :(得分:0)
无论您选择使本地存储库同步的过程如何,最终结果都是相同的:两个存储库都具有相同的历史记录。在这种情况下,本地版本将被远程版本覆盖。
因此,由于您没有本地更改要保留,因此很可能没有比您认为的再次克隆项目更干净,更快捷的事情了。我会说的去做。
(作为旁注,我看不出这是不好的做法的任何原因。您预见到什么具体问题?)