sh-3.2$ ls
dir
sh-3.2$ cat dir/file
some contents
sh-3.2$ cat Dir/file
some contents
型 你可以看到我们做的更新。但是:
sh-3.2$ git status
On branch master
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)
modified: Dir/file
no changes added to commit (use "git add" and/or "git commit -a")
型 Git知道dir/file和Dir/file访问同一个文件。此外,Git知道我们的 last commit中有一个空的Dir/file。因此,Git * 假设 *,我们希望保留文件名的先前拼写:Dir/file。(注意,名称中有斜杠:没有文件夹,只有文件。)
3条答案
按热度按时间7xzttuei1#
nobalG's answer很好
如果您跑步:
字符串
你应该可以开始了(用
git status
确认)。你不需要向.gitignore
添加任何东西。你也可能实际上不需要git add
任何东西。这个答案的问题在于,它只是一个食谱;它没有告诉你 * 下一次 * 你遇到各种问题时该怎么做。
长
首先,让我们正确地定义问题。我们需要注意以下几点:
***Git存储的是 commits,而不是文件。**也就是说,当你运行
git checkout
或git commit
时,你处理的存储单元是 commit。的确,commits * 包含 * 文件,但这是一个打包处理。(有一些有点笨拙的方法来进行零碎的工作,我们稍后会谈到;它们在特殊情况下很有用。)***任何提交的所有部分都是只读的。**这包括所有存储的文件。它们以永久冻结的压缩格式存储。每个文件的内容也会与其他存储的文件进行重复数据删除。这很重要,因为 * 每个提交都会存储每个文件的完整副本 *,但通过重复数据删除,这意味着大多数提交几乎都与其他提交共享所有文件。文件是只读的这一事实使其能够:从字面上看,不可能 * 更改 * 一个存储的文件,所以如果提交X的文件F1需要与提交Y的文件F2相同的内容,那么它们实际上使用相同的存储内容是完全合理的。
***你看到的和使用的文件 * 不在Git中 *。**在Git中 * 的文件 * 不是你看到的和使用的文件 。这是上一条的直接后果。在任何给定的提交中,存储的文件都是以一种 * 只有Git可以读取 * 的形式存在的, 字面上没有任何东西-甚至Git本身-可以覆盖它们 *,这意味着在Git中的文件对于完成任何实际工作都是完全无用的。所以Git不会尝试这样做。当你选择一个提交来处理时,Git会将提交中的文件复制到一个工作区中,我们称之为“工作树”或“工作树"
现在我们明白了你可以看到和使用的文件 * 不在Git中 *,而在Git中 * 是 * 的文件是以一种特殊的Git特有的形式存在的,现在我们可以理解这里发生了什么。当Git存储文件时,这些文件 * 不在文件夹中 * 并且 * 可以有几乎任意的名称 *。在Git提交中,我们没有一个名为
src
或Src
的 * 文件夹 * 来存储一个名为somefile.txt
的 * 文件 *。相反,我们只有一个名为src/somefile.txt
的 * 文件 ,其中有一个斜杠。1而且-这是我们这里问题的关键-- 这些名称总是区分大小写 *,因此提交可以很容易地将src/somefile.txt
* 和 *Src/somefile.txt
作为两个单独文件包含。提交也可以同时包含readme.md
和README.md
,依此类推。同样,这些文件不是普通的文件。它们以一种特殊的、只读的、Git专用的、压缩的和重复数据消除的格式存储,只有Git可以读取(除了一次初始创建以进行新的提交之外,什么都不能写)。而且,它们区分大小写,可以拼写出你在Windows机器上实际上无法使用的文件名。2
如果您使用的是典型的Mac或Windows系统,则您的 * 常规 * 文件是保留大小写但不区分大小写的。这实际上是特定于文件系统的,并且很容易设置一个macOS可挂载磁盘,其中的文件是区分大小写的,这样您就可以 * 存储
readme.md
* 和 *README.md
。请参阅my answer here了解在macOS上执行此操作的方法。在Windows上,你可以设置一个虚拟机并运行Linux;我不使用Windows,所以我没有现成的过程。1从技术上讲,committed 版本的文件在内部使用类似于文件夹的结构;文件夹的位置是Git的 index。但是你不能直接处理提交:你把现有的提交读到Git的index中,然后在Git的index中建立新的提交,这里的文件名实际上包含了斜杠。所以我们不妨说文件名中有斜杠。
2 Mac用户可以嘲笑Windows用户无法创建名为
aux.txt
的文件,因为在Mac上创建aux.txt
很容易。但Mac用户还有其他问题:例如,有两种拼写文件名agréable
的方法,其中只有一种在Mac上有效。虽然有时这是一件好事-我们可能不希望有两个不同的文件似乎具有相同的名称-它带来了完全相同的互操作性问题。在macOS上设置问题案例
由于我手边有一台Mac,我可以举例说明我们如何设置一个问题案例。我们从一个新的、完全空的仓库开始:
型
现在,到目前为止,我们所做的在Git本身是创建一个初始空提交,其中有一个名为
Dir/file
的文件。然而,最后两个命令告诉macOS修改我们的 * 工作区 *-我们的工作树-将Dir
,initial-token重命名为dir
,token,运行ls
表明操作系统确实重命名了工作树目录,但是Git知道 * 在这个文件系统上 *,dir/file
和Dir/file
都可以 * 读取或写入同一个普通文件 *。所以:型
你可以看到我们做的更新。但是:
型
Git知道
dir/file
和Dir/file
访问同一个文件。此外,Git知道我们的 last commit中有一个空的Dir/file
。因此,Git * 假设 *,我们希望保留文件名的先前拼写:Dir/file
。(注意,名称中有斜杠:没有文件夹,只有文件。)深入了解:Git的index和
core.ignorecase
这里涉及到多个相互交织的部分:
我喜欢将Git的索引总结为 your proposed next commit。这是它作为 staging area 的角色。但它并不完全完整,我们稍后会看到。尽管如此,这里的关键思想是,当你第一次检查某个特定的提交时,Git * 复制 * 所有提交的文件 * 到 * 它的索引。2索引--建议的 * 下一次 * 提交--现在匹配 * 当前 * 提交。如果你做了更改,你必须让Git把更新后的文件复制到索引中,这就是
git add
的作用。请记住,索引中有文件的 * 全名 *,并带有正斜杠。这些文件都保存在数据文件中--目前是
.git/index
,可能还有.git
目录中的一些其他文件--这些文件不是由操作系统管理的,而是由Git管理的。所以它们由Git决定,Git说这些文件名是区分大小写的,因此,索引可以同时存储readme.md
* 和 *README.md
,或者同时存储dir/file
* 和 *Dir/file
。readme.md
* 和 *README.md
。您将无法同时存储dir/file
* 和 *Dir/file
。您的操作系统将dir
和Dir
视为 * 一个文件夹名 *,readme.md
和README.md
都是一个 * 文件名 *。所以事实上,*Git的提交可以在这里保存两个名称 *,但 * 你的操作系统的文件系统不能 *,这导致了我们的问题。git init
时间-Git * 探测你的文件系统是否忽略了大小写 *。如果你的操作系统 * 忽略了大小写,那么这个问题就会发生,Git会将core.ignorecase
设置为true
:型
Git使用它来“知道”
Dir/file
和dir/file
,虽然与 Git 不同,但与 * 您的主机操作系统的文件系统 * 相同。2索引中的实际内容是:
100644
(读/写)或100755
(读/写,但也可执行);由于内容实际上是在其他地方,索引并不持有文件的真正副本。然而,副本管理是全自动的。这意味着索引副本就像文件的副本一样,如果可能的话,不占用任何磁盘空间(如果内容重复了一些现有的提交文件)。
所有这一切的最终结果是,每个文件的索引“副本”都准备好进入 * 下一次 * 提交。换句话说,索引中的内容 * 就是 * 暂存的内容。但是如果你有一万个文件,当9997个都是一样的时候,把它们都列出来会很痛苦和无聊。所以
git status
并没有告诉你这9997个 * 相同 * 的文件。它只是说这3个 * 不 * 匹配的文件是“staged for commit”。事实上,这一万个文件都是staged的;我们只是保持git status
可用,而不是 * 说 * 任何关于 * 匹配 * 的东西。Git如何使用
core.ignorecase
所以,在上面的设置中,我们:
1.在不区分大小写的Mac或Windows系统上创建了一个新的空存储库,其中不能同时包含
README.md
* 和 *readme.md
文件,也不能同时包含Dir/file
* 和 *dir/file
。如果我们有一个名为Dir
(x1)的目录,并且我们尝试创建dir/another
,则 system 将创建Dir/another
。这不是Git的问题,但Git必须处理它。
1.创建并提交了一个空的
Dir/file
。这现在在一个提交中。它永远不能被更改!这个提交,只要它存在,就有那个路径名,有那个特定的大小写。1.使用一些操作系统端工具(macOS上的普通
mv
,我不确定你在Windows上会使用什么)将Dir
重命名为dir
。由于主机文件系统的不区分大小写的特性,在我们创建仓库时,Git自己将
core.ignorecase
设置为true
。我们现在可以 * 欺骗Git*,说我们的操作系统将这些视为 * 不同的 * 情况。这不是真的!但我们 * 可以 * 实际上欺骗Git,故意的,暂时的。如果出了问题,我们将负责:别这么做除非你愿意承担责任。型
(the第一行是重复的,但是因为我使用了
>
,所以我可以尽可能多地重复它--它会擦除文件,并在其中插入一行阅读some contents
)。在这里,我使用
git status --short
来缩短输出。M
表示工作树副本已经被修改。在欺骗Git之前,Git检查并看到dir/file
存在,* 发现 * 这与索引Dir/file
匹配,并将它们匹配起来。然而,一旦我欺骗Git,有趣的事情发生了:Dir/file
。它试图打开那个文件,假设它不会得到dir/file
。但是它确实得到了dir/file
。它将这个dir/file
与它认为得到的Dir/file
进行比较,发现它是不同的。所以现在Git说这个文件是 * 修改的 *(在工作树中状态为M
)。dir
的目录/文件夹,读取它,并找到了名为file
的文件。在Git的索引中,路径dir/file
是 not,所以Git说这个文件是 untracked(这是双问号)。我现在可以
git add dir/file
:型
Git使用文件名将
dir/file
复制到索引中。* 此 * 文件副本的内容为some contents
。git status
输出现在显示为 new 文件。这是因为 * 当前提交 * 没有dir/file
;它只有Dir/file
(具有不同的内容)。我现在可以创建一个新的提交了。这个新的提交包含
Dir/file
-仍然是空的-和dir/file
,内容为some contents
:型
这里的
blob
行是Git表示有两个文件的方式。这两个文件的模式是100644
(读/写但不执行)和丑陋的哈希ID是Git消除重复内容的方式;文件的名称出现在右边。git ls-tree
的-r
选项需要让它显示每个“文件夹”中的内容(另见脚注1:如果不是因为索引将文件夹删除,Git * 将 * 能够存储一个空文件夹,但因为索引做了它自己的事情,Git不能)。现在我已经提交了 this,这个冻结状态--有两个不同的名字--cases--永远存在,或者至少,只要第二次提交继续存在,我就能做到这一点,即使是在不区分大小写的Mac文件系统上,通过欺骗Git的技巧。
在恢复
core.ignorecase
之前,我现在可以做最后一个技巧:型
实际上我已经可以恢复
core.ignorecase
了,因为git rm --cached
不需要做花哨的大小写技巧:它总是直接从Git的索引中删除条目,而且这些条目总是区分大小写的。但是我想我应该像这样展示它。让我们把core.ignorecase
放回它应该的样子,然后提交结果:型
我做了一个正确的提交,包含了我想要的内容,现在我有了一个可以在Mac上正确使用的提交,即使是在一个不区分大小写的文件系统中。中间的提交,包含
Dir/file
* 和 *dir/file
,* 不能 * 在典型的Windows或Mac设置中正确使用-但是因为Git在进行新提交时实际上使用了 index,我们可以,这并不有趣,更好的工具可能会更好,但这显示了如何克服一些问题。请注意,当你关闭
core.ignorecase
时,* 你 * 有责任让文件名大小写正确。Git会错误地认为创建dir/file
会创建dir/file
,即使存在一个名为Dir/
的文件夹。你必须手动rm -r
整个Dir
目录,或者重新命名它:型
例如,这样当Git去创建
dir
时,操作系统就不会轻松地使用现有的Dir/
目录。在您完成这种手动的、仔细的、一次一个文件的挖掘之后,重新打开
core.ignorecase
绝对是一个好主意(假设它最初是打开的:使用git config --get
来找出答案)。请注意:
告诉Git从 * 某个特定的提交 * 中提取给定的
<path>
:1.将其复制到Git的索引中:关闭
core.ignorecase
后,Git会认为使用不同大小写的类似名称是可以的;1.将生成的索引文件复制到你的工作树中;这是 * 你 * 有责任确保任何现有文件夹都有正确的大小写的地方。
这就是我上面所说的“零碎”,在长部分的顶部的方式。形式:
告诉Git从 * Git索引中的当前内容 * 中提取给定的路径。
我们在一种情况下使用提交说明符,而在另一种情况下省略它,这是令人困惑的。如果你有Git 2.23或更高版本,你可以使用
git restore
而不是git checkout
。restore
命令允许你指定某些内容的来源为提交或索引。如果你选择一个提交,你可以指定文件是否被复制到索引,或者你的工作树,或者两者。如果你选择索引作为源,你唯一可以复制文件的地方就是你的工作树。(The最后一点是因为你的工作树和Git的索引都可以被写入,但是提交只能被读取。
qnyhuwrf2#
从git中删除目录,但不删除文件系统
字符串
将目录添加到.gitignore,然后记住在那之后做一个git push。
对于类似场景,checkout this brilliant thread
yeotifhr3#
从1.5.6版本开始,.git/config的[core]部分中有一个可使用的“删除”选项。
例如
add_release = true
要仅为一个repo更改它,请从该文件夹运行:
git config core. configure true全局修改:
第一个月