首页 > 解决方案 > 如果我在 Github 上更改文件名,然后将这些更改拉入本地存储库,文件名是否也会在本地更改?

问题描述

假设我的电脑上有一个名为 index.html 的文件,我将它推送到我的 Github 帐户。然后从 Github 帐户中,我将文件名更改为 index.php,并将这些更改拉回我的本地存储库。我的电脑上的文件名会自行更改为 index.php,还是我需要做任何事情?

标签: gitgithub

解决方案


TL;博士

一般来说,您不必担心这一点。它只是工作。甚至不要将文件视为“重命名”,只需将它们视为存在或不存在即可。如果您index.html完全删除,然后创建一个与 old 具有相同内容的全新,index.php实际上index.html与将old重命名index.html为 new有什么不同index.php吗?

(这是一个反问。答案可能是否定的,也可能是肯定的。想想否定的情况和肯定的情况。然后想想答案是肯定的情况中是否有,这是不同的适用于 Git 存储库。它们适用吗?如果是,它们为什么适用?你的存储库是如何进入那种状态的?)

关于 Git 要意识到的一点是,它与文件无关,甚至与分支无关。相反,Git 是关于提交的。

在 Git 中,每个提交都有一个唯一的编号。这些不是简单的计数:它们不是提交 1、2、3 等等。但是每个人都有一个号码。这个数字很大,通常以十六进制表示为哈希 ID。每个提交都有自己唯一的哈希 ID。没有其他提交可以拥有该 ID:该 ID,一旦该提交拥有它,现在将保留给该特定提交。ID 的排列是为了让每个Git 都同意ID 现在保留给提交。1 这个唯一的编号是您的 Git 如何在您自己的存储库中找到您的提交,以及您的 Git 将如何共享这个提交与其他一些 Git 存储库。这个编号系统强加了一个铁的约束:任何现有的提交都不能改变,永远。 您可以进行所有您喜欢的提交,但您不能更改现有提交;甚至 Git 都做不到。2

除了通过 Git 找到它们的唯一编号之外,关于提交要了解的是它们存储两部分:数据元数据。提交中的数据包含每个文件的完整快照。元数据包括提交人的姓名和电子邮件地址、提交时间等。对于每个提交,元数据还包括一个提交的哈希 ID 。这些字符串一起提交到一个向后看的链中,因此如果我们知道链中最后一个提交的哈希 ID,我们可以使用它来一一查找所有较早的提交。

提交中的源快照包含每个文件(即存在于该提交中)。这些文件以一种冻结的、只读的、仅限 Git 的格式存储,可以对文件进行重复数据删除。这样,如果您保存同一个文件数千次,它不会占用额外的空间:所有提交实际上共享该文件的一个副本。但是,虽然这些文件非常适合检索提交,但它们对于完成任何新工作完全没有用,因为您无法更改它们。因此, Git 中的文件并不是您在工作时实际使用文件。

了解了所有这些,现在想想这个:不管你如何实现它,要“重命名”一个文件,你必须采取一些现有的提交,其中包含一些文件,将所有这些文件提取到工作区,然后让 Git删除该文件并创建一个具有不同名称的新文件(但与旧文件的内容相同)。然后你让 Git 做出一个新的提交,而不是index.htmlcontains , contains index.php。因为新文件的内容与旧文件的内容相匹配,所以新的提交不需要空间来保存新文件,3但你确实需要一个新的提交,因为没有什么可以改变现有的。

因此,在非常真实的意义上,没有提交会命名文件。每个人都有它拥有的任何文件。当比较旧的提交和新的提交时,Git 所做的是查看旧提交是否完全删除了一个文件,而新提交是否会创建一个全新的文件。如果是这样,也许Git 应该将其称为重命名而不是删除和添加。Git 将这个决定是否某个旧文件重命名的过程称为重命名检测。您可以在执行git diff.

当您git pull进入图片时,事情会变得更加复杂。


1这个特殊的魔法是通过使用加密哈希来完成的。Git 计算提交内容的哈希值。由于每个提交都具有唯一的日期和时间戳及其源快照所有其他元数据,因此该唯一内容的哈希 ID 又是唯一的。

鸽巢原理告诉我们,这种技术最终一定会失败。哈希 ID 足够大,以至于实际故障非常罕见,以至于我们从未见过。

2这意味着这git commit --amend是一种谎言。旧的提交保留在存储库中:git commit --amend做一个新的和改进的提交,并让 Git 使用新的而不是旧的。

如果旧的从未在任何地方使用过,Git 最终会删除它,但有关此的详细信息超出了此答案的范围。

3从技术上讲,Git 需要一点空间来记录新文件的名称。 大多数提交需要一点空间来记录提交的文件集;当新提交包含与以前的提交完全相同的文件和内容时会发生异常,因此区分旧提交和新提交会说它们是 100% 相同的。


git pull= git fetch+ 另一个 Git 命令

做什么git pull有点复杂,因为它运行两个Git 命令。4 首先,它运行git fetch。这一步从其他 Git 获取新的提交,例如 GitHub 上的 Git。

的全部细节git fetch可能会变得非常复杂,但简化后的画面非常简单:您的 Git 调用了其他 Git。另一个 Git 负责一些存储库。该存储库中有一组提交,其他 Git 通过该其他存储库的分支名称找到这些提交。另一个 Git 将这些名称以及与每个名称关联的提交哈希 ID 告诉您的 Git。

你的 Git 现在检查你自己的存储库:我有那个提交号吗? 如果你有那个提交,在那个数字之下,这个特定的阶段git fetch就完成了。如果没有,您的 Git 会要求他们的 Git发送该提交,然后他们会立即提供之前的提交。你的 Git 会检查你是否有那个,如果没有,你的 Git 也会要求那个。这会重复,直到他们发送您确实拥有的提交编号。通过这种方式,您的 Git 从他们的 Git 中获取他们拥有的所有提交,而您没有。

一旦你的 Git 拥有所有这些提交,你的 Git 就会获取它们的分支名称并将它们重命名为你的远程跟踪名称。如果他们有master,您的 Git 会将其更改为origin/master. 如果他们有develop,您的 Git 会将其重命名为origin/develop. 所以你最终会得到一堆origin/*名字。您的git fetch操作现在更新您的远程跟踪origin/*名称,以便您的 Git 记住他们的 Git 分支在此时间的位置git fetch5

一旦git fetch获得新的提交和更新的远程跟踪名称,它就完成并退出。这允许git pull运行它的第二个 Git 命令。

第二个 Git 命令的选择取决于您。您可以对 Git 进行编程以在git rebase此处运行。默认使用的命令是git merge. 6 在任何一种情况下,都指示第二个命令在您告诉使用的分支中与第一个命令获得的最后一次提交git pull合并或重新基于该提交,如向另一个Git 询问分支时所见。git pullgit fetch

因此,如果您告诉git pull使用git rebase,那么:

git pull origin master

大致相当于:

git fetch origin && git rebase origin/master

如果你告诉git pull使用git merge(或不指示它使用),它大致相当于:

git fetch origin && git merge origin/master

所以这取决于三件事:

  • 你让它运行的第二个命令(包括你git pull传递给第二个命令的任何选项);
  • 他们有什么新的提交master到达你的origin/master;和
  • 与新提交相关当前分支中的提交

4在不久的过去,这确实是真的。这些天来,git pull已经用 C 重写,而不是一个 shell 脚本;它现在直接内置了其他命令。但它仍然像以前一样工作,只是现在效率更高了。

5此信息已过时。它过时的速度取决于其他人向其他 Git 存储库添加新提交的速度。在高度活跃的存储库中,您git fetch可能会在几秒钟或几分钟内过时。不过,大多数存储库都不是那么活跃——如果另一个存储库在您的控制之下,在 GitHub 上,您可能是唯一可以向其中添加新提交的人。

6在一种情况下,git pullgit checkout作为其第二个命令运行,但这不是您直接控制的。如果您将其git pull用作手动克隆某个存储库的最后一步,而不是使用git fetch后跟git checkout.


简化假设以保持这篇文章简短(如果还不算太晚)

我们可以在这里做一些简化的假设:

  1. 您的本地存储库和您在 GitHub 上的存储库同步的。
  2. 您在 GitHub 上的存储库中添加了一个或两个新提交。(或者,无论如何,一些小的有限数。)
  3. 您也没有在本地完成任何未提交的工作,并且没有现有的未跟踪工作树文件会干扰接下来的几个步骤。
  4. 您正在使用git merge并允许它快进。
  5. 没有其他条件阻止这种快进操作。

在这种情况下,您的git pull origin master或您运行的任何类似命令只会找到一些新的提交,因此能够执行此快​​进操作。这意味着git merge根本不需要进行合并。7

此时您的 Git 将看到您当前有一个名为index.html. 8 您的 Git 将看到在您即将移至的提交中,没有名为index.html. 您的工作树中也没有未保存的工作index.html。但是,index.php在新提交中命名了一个文件,并且您index.php的工作树中没有以方式命名的文件。因此,您的 Git 将完全删除index.html文件——这是安全的,因为它保存在当前提交中——然后创建一个index.php包含与新提交相同内容的全新文件,这当然与现有提交的内容相匹配。

一旦 Git 的索引和你的工作树匹配的提交——通过完全删除一些旧文件并“从头开始”添加一些新文件来实现——快进“合并”操作基本上完成了。Git 现在更改您的分支名称以识别新提交,以便 master现在拥有与您的origin/master. 由于您origin/master拥有与 GitHub 存储库相同的哈希 ID master,因此您的设置和它们的设置再次相等。

该文件index.html已被完全删除,并且该文件index.php从头开始创建的,但除非您有办法告诉这确实发生了,否则您将无法将其与现有的index.html重命名区区分开来。在 Unix 或 Linux 系统上,判断的方法是检查 inode 信息(inode 编号可能会改变,尽管这不能保证),使用文件系统监视器(它将看到单个事件,而不仅仅是最终结果),和/或使用硬链接以使 inode 数量显着。如果您不打算这样做,您可能不会关心操作是否“就地重命名”。


7尽管 Git 称其为快进合并,但它根本不是真正的合并。它只是git checkout将分支名称向前拖动。

8index.html从技术上讲,Git 知道这一点,因为在 Git 的index中有一个“副本” 。索引跟踪(保留“副本”)将进入您进行的下一次提交的所有文件。(“复制”这个词在这里用引号引起来,因为 Git 使用去重格式,所以如果 Git 索引中的内容与存储库中任何位置的任何现有文件匹配,则没有实际的副本,只有名称。)签出提交包括调整 Git 的索引以反映刚刚签出的提交,因此索引和当前提交通常匹配——嗯,直到您开始使用git add或其他 Git 命令来修改它以准备进行另一个提交。


推荐阅读