远程 git 存储库中的文件重命名失败



我又对 git 有点迷茫了。我的情况是这样的:

我有一个远程存储库,我在其中push我的本地存储库。然后我注意到我里面有 2 个 docker 文件。一个叫Dockerfile另一个叫Dockerfile (local_model)。我还注意到我只需要 1Dockerfile这必须Dockerfile (local_model).

换句话说,我需要删除Dockerfile,然后将Dockerfile (local_model)重命名为Dockerfile

我使用这些命令来实现它:

git rm --cached path/to/Dockerfile
git commit -m "Deleted a file"
git push -u origin master

删除了远程存储库中的文件(我在浏览器上检查了它,它似乎已经工作了)。

然后我使用了:

git mv "path/to/Dockerfile (local_model)" "path/to/Dockerfile"

致命:目标存在, 源=路径/到/Dockerfile (local_model), 目标=路径/到/Dockerfile

这使我无法重命名该文件,因为存在另一个同名文件。我设法使用以下方法重命名文件:

git mv --force "path/to/Dockerfile (local_model)" "path/to/Dockerfile"

毕竟(之后也应用commit ...push..),但我感到困惑。

与目标文件同名的文件在哪里?我知道此命令仅影响远程存储库,因此与本地存储库不会发生任何冲突,对吗?

eftshift0 的评论给出了答案:Git 抱怨的文件,git mv --force覆盖的文件是工作树副本。 这里发生的事情是 Git 有很多文件副本!

提交是永久性的(大部分)和只读的,并且永久冻结文件

使用 Git 进行新提交时,请务必记住几件事。 首先,提交本身是永久记录。 每个提交是或具有:

  • 您告诉 Git 冻结到该提交中的所有文件的快照;
  • 您的姓名(
  • 作为作者和提交者),或作者/提交者的姓名(如果提交是由其他人进行的);
  • (或其他人)关于您提交原因的日志消息; 以及 - 这部分至关重要,但我们不会在这里介绍它,因为我们还不关心 Git 的历史记录是如何工作的——提交的父级或父级的哈希 ID

每个提交的实际名称是一个丑陋的大哈希 ID,当你(或任何人)提交时,Git 当场编造了它。 Git 保证每个丑陋的大哈希 ID 都是唯一的,因此每个提交都有不同的哈希 ID。 一旦完成,没有人 - 无论是你还是 Git - 都不能更改提交:如果你尝试这样做,并且实际上确实更改了一些东西并再次提交,你得到的是一个不同的提交,具有一个新的和不同的哈希ID。 旧提交也保留在存储库中。

在某些情况下,你可以告诉 Git忘记提交(实际上是整个提交链),这样你就再也找不到它们了;最终 Git 会清理它们并删除它们。 但大多数情况下,我们对 Git 存储库所做的是添加新提交,同时保持所有现有提交不变。 我们根本无法更改任何现有的提交,因此一旦完成,它就会冻结所有文件的副本,永久保存,以防您需要将它们从深度冻结中恢复出来。

冻结的文件是压缩的,只有 Git,所以我们必须有一个工作树

因为 Git 会永久(大部分)保留您(或任何人)告诉它保存的每个文件的每个版本,如果 Git 只是按原样保存这些文件,这些文件将迅速占用您的所有存储空间。 因此,冻结的文件被压缩,有时非常压缩,以一种特殊的仅限 Git 格式,在所有提交之间共享。 新冻结 - 新提交 - 只要有可能,只需重用旧提交的旧冻结。 这一切都是自动的,在幕后,但它有一个强大的后果。

如果 Git 没有办法重新构建/重新水化您的冻干文件,您将永远无法取回它们中的任何一个! 不会有git checkout. 一句话,那将是糟糕的。 因此,Git 为您提供了一个空间,您可以在其中提取(和解冻)您的文件,将它们重新转换为可用的文件。 这个空间是你的工作树或工作树

一般来说,你用git checkout告诉 Git:我希望在这里提交。你使用像master这样的名称,但你实际上是通过其唯一的哈希 ID 来选择提交。 Git 重新构建文件并将它们放在您的工作树中,以便您可以访问它们并处理它们。

Git 会记住你选择的提交(及其分支名称):这些是你当前的提交。 Git 通过将名称HEAD附加到分支名称来实现此目的。1它仍然是您当前的提交,直到您执行某些操作来更改哪个提交是您当前的提交 - 包括git checkout,但也包括进行新的提交,然后成为当前提交。 无论如何,很明显,在这一点上,我们每个文件都有两个副本。 如果你有一个名为README.txt,我们可以称其中一个为HEAD:README.txt,另一个只是普通README.txtHEAD副本是仅限 Git 的,并且始终处于冻结状态,工作树副本是正常的。

Git 可以停在这里——它可以将冻结的仅限 Git 的文件永久保存在提交中,再加上你正在处理或使用的一个工作树副本。 但是 Git 在组合中添加了第三个副本。


1HEAD附加到某个分支名称足以记住当前分支当前提交,因为分支名称会记住提交。 你可以问 Git:HEAD的分支名称是什么?你得到分支;或者你可以问 Git:HEAD的提交哈希 ID 是什么?Git 使用分支名称来查找提交。


索引

Git 在这里使用了一个技巧,部分是为了速度,部分是为了各种其他目的。 Git 不是将文件从深度冻结中取出并重新构建它们并忘记它做了什么,而是将文件从冰箱复制到更方便的位置。 在这里,它们仍然以 Git 的高度压缩形式保存,但现在它们是解冻的。

第三个区域(实际上是提交和工作树之间的中间区域)被称为索引暂存区域缓存,具体取决于 Git 的哪位/哪位在调用。 这个指数做的第一件事就是它使git commit变得快。 由于索引(或暂存区域)中的文件始终已经采用正确的形式,因此git commit要做的就是冻结它们。 其他版本控制系统必须遍历整个工作树,仔细检查每个文件以查看它是否已更改,这甚至可能涉及将文件重新压缩到其冻结形式。 这需要很长时间。 Git 不必这样做。

但这意味着当您工作时,每个文件都有第三个版本。 不仅有一个与你的工作树README.txt一起使用的HEAD:README.txt,还有一个README.txt索引副本,我们可以称之为:README.txt

当您对文件运行git add时,Git 会压缩该文件的工作树版本,并替换之前索引中的工作树版本。 或者,如果它以前根本不在索引中,那么现在它是。 无论哪种方式,新版本的文件现在都可以使用了 -git commit所要做的就是冻结预压缩文件!

这就是git rm的用武之地。 如果你有一个文件在你的工作树中,因为它在提交中,你(或者无论如何 Git)现在触手可及的文件的三个副本HEAD:file在当前提交中,:file在索引中,file在工作树中。 运行git rm file会删除两个副本:索引中的一个副本和工作树中的一个副本。 (它不能碰到冷冻的,因为,嗯,它是冻结的! 现在该文件不在索引中,它不会出现在您进行的下一次提交中。

但是,如果您运行git rm --cached,Git 所做的就是删除索引副本。 现在你有HEAD:filefile但没有:file. 这意味着您进行的下一次提交中不会包含文件file。 但是,很难看到您做了什么,因为每个文件的索引副本通常是不可见的。 如果您使用普通的计算机工具查看您拥有的文件,则会显示工作树文件。

每个文件的HEAD副本通常也是不可见的,这具有相同的问题 - 但由于提交的文件被冻结,我们通常不在乎。 如果我们想查看文件,我们只需使用git checkout切换到其他提交。 这会更新索引和工作树,以匹配其他提交。 然后我们可以只查看工作树,因为所有文件的所有三个副本都匹配。

git status以简短且有用的形式告诉您一切

Git从索引进行新的提交。 因此,索引决定了您将提交的内容。 这就是为什么我们可以将其描述为您进行的下一次提交中的内容。但该指数基本上是看不见的。 你怎么知道你所做的下一次提交会是什么?

运行git status是了解索引情况的方式。

git status做的第一件事是告诉您当前的分支,即,它查看特殊名称HEAD附加到哪个分支名称。 这会告诉您下一次提交将转到何处。

git status输出的其余部分告诉您为提交而暂存的文件未为提交而暂存的文件git status首先要做的不是列出索引中的每个文件(在一个大项目中,索引中会有很多文件),而是比较每个文件的索引副本,看看自HEAD副本以来发生了哪些变化。 它不需要确切的区别,只需要快速扫描索引副本是相同还是不同(这是 Git 可以非常快速地完成的事情,因为它的内部冻结格式。

如果索引中的文件(:file副本)与HEAD:file副本不同,Git 会告诉您此文件是为提交而暂存的。 您知道它将在下一次提交中,但更有用的是,您知道它在新快照中会有所不同。 比较旧的(当前HEAD)和建议的新提交,file发生变化

同时,git status还将:file的索引副本与工作树副本file进行比较。 如果这两者不同,Git 会告诉您此文件不会暂存以进行提交。 这告诉您现在可以运行git add以将file复制到:file

请注意,您可以让file的所有三个版本都不同! 在这种情况下,它staged for commitnot staged for commit。 当然,您可以拥有新文件 — 不在HEAD中的文件,但现在位于索引和工作树中。 您可以删除文件,即在HEAD但现在不在索引和工作树中的文件。

在索引中是使文件被跟踪的原因

文件的索引副本和工作树副本是分开的。 这意味着您可以拥有一个位于工作树中的文件,但不在索引中。 此类文件是未跟踪的文件。 请注意,文件是否在提交HEAD中并不重要! (如果它在提交HEAD中,则必须在索引中,并且您已将其从索引中删除,只是尚未提交。 运行git status会告诉您有关此类文件的信息,抱怨它们未被跟踪。

您还可以拥有一个位于索引中但不在工作树中的文件。 这是一种有点奇怪的状态,但如果您使用非 Git 命令删除文件,就会发生这种情况。 Git 并不太关心你是否这样做了——它仍然会使用索引副本进行新的提交——但git status会告诉你:该文件将在索引树与工作树的比较中删除。

你可以让 Git 对未跟踪的文件闭嘴,方法是在你的.gitignore文件中列出它们的名称(或像*.tmp这样的glob 模式)。 这不会使文件无法跟踪! 这只会让 Git 对他们闭嘴。 它还有其他一些效果,但主要的是关闭 Git。 这很有用,因为它使git status输出更短

git status只告诉你你需要知道的事情时——当 Git 运行从HEAD到索引的git diff时,有些文件是不同的,当 Git 运行从索引到工作树的git diff时,有些文件是不同的——那么git status就变得尽可能有用。 您不必有选择地选择其输出来查找有用的内容,因为文件 X 和 Y 是暂存的,而 Z 未暂存正是有用的东西。 如果您现在提交,则在下一次提交中两个文件将有所不同;如果您git add文件Z,它也可能有所不同。

如果您有选择地仅从索引或仅从工作树中删除文件,您会发现自己处于这些相当不寻常的极端情况中。 其中一些确实有道理;Git 不会阻止其中任何一个。 Git 通常会尽量小心不要破坏未跟踪的文件,因为它们不会永远冻结到新的提交中,这与跟踪的文件不同。

最新更新