Git:无论是否删除它,都有一个文件显示它已更改,如何保持不变?

时间:2018-10-31 19:33:25

标签: git

我认为有人更改了文件的大小写,使我无法在PR中将其保持不变。在完成git pullgit rebase -i origin/master之后,git status显示我添加了一个我从未碰过的文件。我尝试运行git rm <filename>,但是它表明我删除了该文件。

我运行了git checkout origin/master -- <filename>,它重新添加了文件,但是git status显示我再次添加了文件。

我注意到在删除文件后创建提交时,它显示两个不同的大小写。即:

[branchname 45dd45ce2] wip
 2 files changed, 140 deletions(-)
 delete mode 100644 Filename
 delete mode 100644 filename

我尝试将文件重命名为两个大小写,但是Git始终将其视为新文件。

如何保持不变?

我在Ubuntu 18.04上使用WSL。

2 个答案:

答案 0 :(得分:1)

TL; DR:您的系统不如实际的Linux功能强大,这已给您带来了痛苦

进行提交的Linux系统存储了两个文件,其名称仅大小写不同。您的系统无法做到这一点,因此您不能在自己的系统上的这个框架内工作,至少不能直接工作。如果您在Windows系统中启动Linux VM,则可以在那里使用它。但是有 一种通用方法可以在您自己的系统中处理此问题,我将在较长部分的最后一部分中进行展示。它有一些缺陷,但可以让您取得进步。

(实际上,最好的解决方案是启动Linux实例并直接对其进行修复。)

  

我注意到在删除文件后创建提交时,它显示两个不同的大小写。即:

[branchname 45dd45ce2] wip
 2 files changed, 140 deletions(-)
 delete mode 100644 Filename
 delete mode 100644 filename

这意味着您已签出的提交(新提交45dd45ce2的父提交)中包含该文件名的两个拼写。 Linux可以做到,但Windows无法做到。 1


1 从技术上讲,这是文件系统特定的。在折叠情况下的文件系统上会发生此问题,Windows和MacOS默认情况下会这样做,而Linux默认情况下不会这样做。显然,WSL默认使用底层Windows文件系统,从而导入其功能和限制。


让我们先退后一步,看看Git对提交的真正作用。请记住,一次提交包含某些文件集的完整快照。对于每个文件,提交存储区是文件的名称,其执行许可标志及其内容。提交本身由唯一的,丑陋的唯一哈希ID标识,例如4ede3d42dfb57f9a41ac96a1f216c62eb7566cc2(这是Git仓库中Git本身的提交)。该提交还存储日志消息,提交作者的姓名和电子邮件地址以及父哈希ID,以及其他数据。但是目前,我们将专注于通过提交存储的文件,尤其要注意它们的名称。不过,首先让我们简单地看一下内容,因为这部分也很有趣。

每个文件都存储在每个提交中。如果Git每次都存储一个新副本,这将很快使您的存储库很大。因此,如果很简单,Git不会存储新副本。特别是,如果 new 提交对其大部分文件使用相同内容,则Git会简单地在新提交中重新使用旧内容 。这意味着Git最好不要触摸现有保存文件的内容,所以不要这样做:它们是只读的。同时,为了使存储库更小,Git还压缩了这些内容。因此,存储在Git存储库中的每个文件的内容都采用特殊的只读(因此可以共享),压缩(有时非常压缩)的Git形式。 (Git称这些 blobs 。“ blob”是四种内部Git对象类型之一,其他三种是 tree 带注释的标签 commit 。同时,名称存储在这些“树”对象中。您无需了解这些详细信息,但有时它们很有用。)

一旦完成,每个提交也是只读的。实际上,所有Git内部对象都是如此。每个对象的哈希ID只是对象数据的加密校验和。这样,当Git稍后再次查看对象时,它就可以确保数据是完整的:数据的当前校验和必须与用于查找的哈希ID匹配。如果它们匹配,则数据是正确的。如果不是,则某种程度上破坏了提交。这是为什么,您无法更改提交:如果更改任何数据,则校验和会更改,而您将获得一个新的不同的提交。但是,我们在这里关心的一点是,提交一旦被冻结,就会及时冻结:其中的任何内容都无法更改,其中包括文件名。

尽管如此,整个Git存储库(以特殊的Gitty形式)可以转移到另一个系统。一旦完成,就可以提取那些提交……好吧。问题就从这里开始。

当Git从存储库中检查出提交时,它必须将冻结的只读blob从冷冻机中复制出来,解冻它们,然后将其放入日常格式,以便您可以实际使用文件。 Git分两个步骤执行此操作:首先将冻结的对象复制到Git的 index 中,该对象处于未冻结状态,但仍采用特殊的压缩Git-only格式,使用Git自己的内部方法记住文件名并执行-permission位,然后将冻结的blob解压缩到您的工作树中。您可以在其中工作。

这最后一步是出问题的地方。 Git需要创建一个名为Filename的文件,以及另一个名为filename的文件。在 Linux 上,这很容易:只需使用两个名称调用文件创建器。在 Windows 上,如果执行此操作,则 second 文件将覆盖第一个文件,并保留您首先使用的名称。

这意味着无论您做什么,最终都只会在工作树中得到一个一个文件,即使您的提交中同时包含了这两个文件(采用仅Git冻结的特殊格式) )和您的索引中(采用冻结专用的Git格式)。这种情况是困难和痛苦的。但是,Git使 new 从索引提交,因此还没有全部丢失。

一种解决方法

您可以在Windows或MacOS系统上(如脚注1所示,Mac文件系统也存在相同的问题)进行 new 提交,其中索引中的两个名称之一具有被重命名。我首先创建了一个包含三个文件的存储库:

$ mkdir case
$ cd case
$ git init
Initialized empty Git repository in ...
$ echo test case issues > README
$ echo THIS FILE USES UPPERCASE > FILENAME
$ echo this file uses lowercase > filename
$ ls
filename        FILENAME        README
$ git add *
$ git commit -m initial
[master (root-commit) 46e94a6] initial
 3 files changed, 3 insertions(+)
 create mode 100644 FILENAME
 create mode 100644 README
 create mode 100644 filename

然后我将此存储库克隆到Mac:

$ git clone ssh:[url]
Cloning into 'case'...
remote: Enumerating objects: 5, done.
remote: Counting objects: 100% (5/5), done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 5 (delta 0), reused 0 (delta 0)
Receiving objects: 100% (5/5), done.
$ cd case
$ git status --short
 M FILENAME
$ ls
README          filename
$ git ls-files
FILENAME
README
filename

现在的诀窍是重命名索引中的两个文件之一。我总体上不喜欢全大写字母,所以让我们现在将大写字母重命名:

$ git mv FILENAME UC-FILENAME

(也许我应该将mv-ed命名为yucky-filename :-))。可以使用git ls-files来检查它是否起作用(或使用git ls-files --stage来获取详细版本),我做到了,但是接下来我只显示提交: 2

$ git commit -m 'fix case-collision'
[master 7712644] fix case-collision
 1 file changed, 0 insertions(+), 0 deletions(-)
 rename FILENAME => UC-FILENAME (100%)

现在,我们必须修复工作树,该工作树与索引和存储库不同步。最简单的方法是使用git reset --hard

$ git reset --hard
HEAD is now at 7712644 fix case-collision
$ ls
README          UC-FILENAME     filename
$ cat UC-FILENAME
THIS FILE USES UPPERCASE
$ cat filename
this file uses lowercase

如果接收存储库是--bare(不是),我们现在可以将其推迟,但要点是我们现在可以本地处理文件(在这种情况下,在特定的Mac上)因为它们不再与本机文件系统冲突。


2 这些是可怕的提交消息。处理真实的存储库时,最好使用一些东西,而不是测试用例。

答案 1 :(得分:0)

重置本地存储库以反映远程存储库应该可以为您解决问题。

git reset --hard

当然,您应该知道重置本地存储库将导致您丢失尚未推送到远程的任何工作。因此,在重置之前请备份目录。 (在这种情况下,我喜欢压缩目录。)