是否可以在拉动过程中从远程使用“.gitignore”?
Is it possible to use ".gitignore" from the remote during a pull?
我遇到这样一种情况,一些以前签入 Git 的文件现在需要忽略。为了忽略它们,我将文件添加到“.gitignore”并执行了以下操作:
git rm -r --cached .
git add --all
git commit -m "Removed files from git tracking that should be ignored"
git push
现在,我需要将这些“.git忽略”更改拉到另一台服务器,但是当我执行 git pull
时,刚刚添加到“.gitignore" 不会被忽略,而是被完全删除!
我认为在拉动过程中发生的事情是使用不忽略这些文件的本地“.gitignore”文件...并且它检测到这些文件不再在 git 所以它只是删除它们。如果我手动添加文件并执行另一个 git pull 然后它开始正常工作(现在服务器上有正确的“.gitignore”文件。)
有什么方法可以告诉 git pull
使用来自远程服务器的“.gitignore”文件而不是本地文件,这样这些文件就会被正确地忽略并且不会被获取在 git 拉动时移除?
在.gitignore
中列出一个文件并不意味着忽略该文件,也不意味着不要删除该文件,或任何您希望它表示的意思。你在这里做的任何事情都不会改变这一点。我们将在这个答案快结束时回到 .gitignore
,但让我们先看看你所处的可怕、可怕的 no-good 情况,你实际上无法 修复。您将不得不以某种方式绕过它。
这是错误的地方
事实是,一些现有的提交 有 这些文件,以及一些其他现有的提交——那些发生在这些文件存在之前的提交,以及那些你在获取这些文件之后所做的提交Git 的索引 out—没有这些文件。没有什么可以改变这些事实。
要理解为什么会这样,请注意 Git 不是 about 文件。 Git 大约 提交 。提交确实存储文件,但这是一揽子交易:全有或全无。你有一个提交,你有它的所有文件。或者,您没有提交,也没有它的文件(因此您将使用 git fetch
将提交添加到您的集合中,然后您拥有它 and 它的所有文件)。
此外,提交中的文件是无用的格式(我们将在下一节中回过头来)。它们被压缩并且 de-duplicated,因为大多数提交大多具有与其他提交中的 相同的 文件。所以 Git 不会将它们 存储为 文件,而是存储为内部 对象 ,它会自动 de-duplicates 它们。
这些对象都有编号,Git 称之为 散列 ID。特别是提交总是得到一个 unique 编号。 (文件,可能是其他提交文件的副本,可能有 non-unique 数字,这就是 de-duplicates 它们。)这个数字实际上是内部对象内容的加密哈希。这限制了 Git:甚至 Git 也不能 更改 提交。
如果您取出一个提交,进行一些更改,然后放回不同的东西,它就会不同,因此会得到一个新的不同的哈希 ID。现有对象保留在 Git 存储库中,位于其现有 ID 下。新的和改进的(我们希望)对象现在 添加 到存储库,在它的新 ID 下。任何使用 old ID 的人都会得到旧对象。使用 new ID 的任何人都会获得新对象。这部分真的很简单。
现在,提交中的数据不仅仅是每个文件的快照。是的,它就在那里,但也有一些 元数据 ,或关于提交本身的信息。例如,这包括提交人的姓名和电子邮件地址。它还包括一个 time-stamp——这有助于确保每个提交都是完全唯一的,因此如果两个不同的人进行 相同的 提交,但出于某种原因,他们都声称是同样的 person,他们仍然会得到不同的 commits 除非他们同时做出他们(在这种情况下,他们真的是两个不同的人?Git 说不)。
因此,每个提交中都有所有这些元数据:作者、提交者、一些 time-stamp、日志消息等。但是在这些元数据中,Git 添加了自己的信息。 Git 在每次提交时存储一组 较早 提交的哈希 ID。大多数提交只存储一个哈希 ID,Git 调用 父提交.
这些父提交哈希 ID 形成 backward-looking 链中的提交。我们从最近(最后)提交的右侧开始。我们不会写出它的真实哈希 ID,而是将此提交称为 H
(对于 H
ash):
<-H
提交 H
中同时包含快照(文件)和元数据,在该元数据中,提交 H
存储了较早提交的哈希 ID。我们假设它的hash ID是G
,画在:
<-G <-H
当然,提交 G
指向一个 still-earlier 提交,它一直向后指向:
... <-F <-G <-H
因为每个提交都指向其父项,Git 可以并且将会找到 整个提交链 如果 Git 只能找到 last 在链中提交。这就是分支名称的来源:Git 中的每个名称——分支名称、标签名称、remote-tracking 名称等等——存储一个哈希 ID。对于分支名称,该哈希 ID 是其链中 last 提交的 ID。即使链中有 still-later 次提交也是如此,这通常发生在开发新内容但尚未将其放在主分支上时:
...--G--H <-- main
\
I--J <-- feature
在这里,feature
分支比 main
分支多了两次提交。提交 J
指向 I
,后者指向 H
,后者指向 G
,依此类推。所以提交 H
和 earlir 在 两个 分支上。提交 I
和 J
目前仅在 feature
上,但如果我们愿意,我们可以“向前滑动名称 main
”:
...--G--H--I--J <-- main, feature
现在所有提交都在两个分支上。
分支名称四处移动,每个名称根据定义 挑选出最后 提交被视为“在那个分支上”。 提交自己 确定那些分支上的早期内容。所以重要的是 commits:名称只是让我们 find 特定的。而且,请记住,所有 提交都被永久冻结。任何现有提交的任何部分都不能更改。
签出提交
正如我们上面提到的,提交中的文件的格式只有 Git 可以使用它们。即便如此,Git 也只能读取。我们需要其他程序能够读取 和写入 到我们的文件。解决方案很简单——并且与其他版本控制系统使用的相同:Git 在某个时候从提交 中复制文件。这些副本一旦脱离版本控制系统,现在就很有用了。事实上,它们只是普通的普通文件:计算机上的任何东西 都可以使用它们。 Git 不再对它们有任何控制。
获取 Git 从某些提交中复制文件的正常日常方法是使用 git checkout
。例如,如果我们有:
...--G--H <-- main
\
I--J <-- feature
并且我们运行git checkout main
、Git将从提交H
中复制所有提交的文件。这也有 select 将名称 main
作为我们的 当前分支 的副作用。由于名称 main
指向提交 H
,这意味着 H
是我们的 当前提交 。我们可以通过将特殊名称 HEAD
附加到名称 main
:
来绘制它
...--G--H <-- main (HEAD)
\
I--J <-- feature
请注意,我们现在每个文件都有 两个副本:H
中有一个我们无法触及的已提交副本,还有一个 ordinary-form Git 称为我们的 工作树 或 work-tree.
的日常文件
在其他版本控制系统中,您只能找到这两个文件副本。1如果您想知道发生了什么,您可以比较 work-tree 一些文件的版本到活动提交的版本:不同的是我们已经改变的。但无论出于何种原因——无论你认为这是个好主意2——Git 存储每个文件的第三个副本3 在 Git 不同的地方,index 或 staging area,或者——最近很少——缓存.
每个文件的第三个副本位于 read-only 提交副本和 work-tree 副本之间。与提交的副本不同,它可以被覆盖。它是 pre-compressed 和 pre-de-duplicated,因此它已准备好进入 next 提交。事实上,这可能是考虑 Git 的索引 / staging-area 的最佳一般方式:它包含你的 提议的下一次提交。 4
所以,当你 git checkout
一些提交时,比如提交 H
,Git:
- 从提交中填写其索引,以便您建议的 next 提交匹配;
- 使用这些相同的文件来填充您的工作树,以便您可以查看和使用您的文件;和
- 将
HEAD
附加到分支名称,假设您使用 main
之类的分支名称来 select 提交。 (如果没有,您将进入“分离的 HEAD”模式,我们不会在这里解决。)
如果您现在对文件的工作树副本进行更改,通常还必须 运行 git add
:这告诉 Git 使索引副本匹配工作树副本。对于您就地更新的文件,这会用新索引副本覆盖旧索引副本。对于您删除 的文件,此删除 索引副本。对于新文件,这会在 Git 的索引中创建一个新文件。
无论哪种方式,添加文件 stages 更改,因为任何时候你 运行 git commit
, Git 都会从那时索引中的任何内容。如果您没有更改索引,则新快照将与当前快照完全匹配。在这种情况下,Git一般要求你使用--allow-empty
标志:新提交实际上并不是空,只是它匹配旧的 snapshot-wise(所以Git想知道:为什么要麻烦?并让你使用标志)。
您是否对 work-tree and/or 运行 git add
进行任何更改以更新 Git 的索引 你的 work-tree, 当前提交 保持不变。一旦你做了一个新的提交,Git:
- 收集元数据;
- 写出快照ad metadata,得到一个hash ID作为结果;和
- 将哈希ID写入当前分支名称。
我们最终得到,例如:
K <-- main (HEAD)
/
...--G--H
\
I--J <-- feature
现在 main
上有一个不在 feature
上的提交。
1non-current 提交中的其他 read-only 副本也可以找到,就像它们在 Git 中一样,但是它们'重新 active 当前提交的方式。
2其他系统没有索引,证明没有索引也是可以的
3这个“副本”是pre-de-duplicated,所以大部分时候,几乎不用space。因此,将其称为 copy 有点误导。然而,与向用户显示的 Git 的许多其他位不同,这个“副本”自动 de-duplicated 的事实实际上是 well-hidden。您可以将其视为每个文件的第三个副本,一切正常。好吧,直到你开始摆弄像 git ls-files --stage
和 git update-index
这样的内部命令:然后你需要了解 git hash-object
.
4索引在冲突合并期间被扩展,这意味着此描述不完整,但至少不是错误。 :-) 索引还可以使 Git 运行得更快,这就是它具有旧名称 cache 的原因。这些天你经常在选项标志中看到这个名字,比如 git rm --cached
.
在具有不同文件的提交之间切换
假设在提交 H
和提交 I
之间,我们 删除了 一个文件。进一步说,我们把它放在一个新的分支 X:
git checkout main
git checkout -b X
git rm somefile
git commit -m 'remove a file'
提交H
有一个名为somefile
的文件并提交I
缺少一个文件名为 somefile
.
当我们 git checkout main
时,文件 somefile
必须 返回 。 Git 将其从提交 H
复制到 Git 的索引和我们的 work-tree,现在我们有了文件。
当我们 git checkout X
返回提交 I
时,文件 somefile
必须 离开 。 Git 将其从 Git 的索引 和我们的 work-tree.
中删除
这个属性是由两次提交中的文件集决定的。我会说完全,但是如果你稍微试验一下,你会发现 Git 对文件 somefile
的 删除 是有条件的:
git checkout main # file somefile comes back
git rm --cached somefile # take somefile out of Git's index
因为我们在这里使用了git rm --cached
,Git从它的索引中删除了somefile
,但是没有触及我们的work-tree副本.如果我们现在 运行:
git checkout X
—记住,提交I
,select由分支名称X
编辑,缺少文件somefile
—Git 不会 从我们的工作树中删除 somefile
。原因是在 git rm --cached
之后,文件 somefile
是 untracked.
未跟踪的文件
Git 中的未跟踪文件只是现在在您的工作树中的文件,但现在不在 Git 的索引中。 就是这样——这就是整个定义——但它有很多后果,包括 git commit
不会在新提交中包含未跟踪文件的事实,以及缺少 删除我们刚刚看到的。
因为你的工作树是你的,你可以随时在其中创建和销毁文件。
因为Git的索引是Git的,Git可以把文件放在那里——但我们知道什么时候它会做哪个东西:
当您 git add
一个文件时,Git 根据该文件在您的工作树中的样子添加或删除该文件。
当您 git checkout
提交时,Git 添加或删除文件 to/from 基于这些文件是否在其他提交中的索引。
当您 运行 git rm --cached
时,Git 按照指示从 Git 的索引中删除文件。
这里没有涉及的其他情况包括git merge
如何操纵Git的索引,git reset
和git restore
如何工作等等。
因此,在某种程度上,你 控制哪些文件在 Git 的索引中——但它们倾向于镜像提交。
索引和工作树特定于每个克隆
Git 对于索引和工作树是否包含在存储库中有点矛盾。具体来说,git init --bare
创建一个没有工作树的存储库,但这样的存储库仍然有一个索引。 (它可能不应该,但确实如此。)还有 git worktree add
命令,因为 Git 2.5,它添加了一对——工作树 和 索引——存储库。因此在任何给定的存储库中可以有多个索引和-work-tree 集。
很明显,git clone
不会 复制任何现有存储库的索引和 work-tree(无论该存储库中存在多少)存储库)。所以指数,或所有指数, 和 work-tree(s) 对每个克隆都是私有的。您无法直接控制任何其他存储库的索引和 work-tree:您必须将其留给可能在另一台机器上操作 Git 的任何人(假设另一个克隆在另一台机器上)。
关于.gitignore
.gitignore
文件命名错误。更好的名称是 .git-do-not-complain-about-these-files-if-they-are-untracked-and-if-they-are-untracked-and-I-use-an-en-masse-add-command-do-not-add-them-to-the-index-either
.
当我们运行 git status
、Git 抱怨未跟踪的文件时。它变得非常抱怨!这很烦人,因为 work-tree 是一个普通目录,而我们使用的软件种类,我们 运行 程序会创建大量构建工件 in 我们的 work-tree 们。这留下了大量未跟踪的文件。 git status
命令变得嘈杂,我们的工作效率直线下降。
为了让 git status
到 关闭 ____ ,我们可以在 .gitignore
文件中列出这些预期的构建产品。 这对这些文件现在是否在索引中没有影响。但是如果它们不在索引中——如果它们现在未被追踪——然后git status
不会抱怨他们。
当然,如果 git status
不抱怨,如果 git add .
也“工作正常”,不添加它们就太好了 .所以这是在 .gitignore
中列出文件的第二个主要影响:如果文件 没有 已经被跟踪——如果它不在索引中 now——而我们运行git add .
,我们希望Git不添加.
如果文件已经在索引中(被跟踪),将其列在 .gitignore
中对 git status
和 git add
没有影响:文件的状态 会检查,git add
会添加文件。5 所以对于already-tracked文件, .gitignore
无济于事。这就是文件名不正确的原因。但是更正确的名称将无法使用,所以 .gitignore
是。
在 .gitignore
中列出一个文件还有一个副作用:它赋予了 Git 对文件 clobber 的权限。这主要涉及检出 确实 包含文件的旧提交,而文件正在 untracked-and-ignored。结帐继续进行,现在您已取出 tracked 文件,而未跟踪的数据实际上已被销毁。所以 真实的 全名可能是 .git-about-some-files-that-may-be-untracked-and-what-to-do-if-they-are:do-not-complain-and-do-not-auto-add-but-do-feel-free-to-destroy-these-files
之类的。 (但许多 Microsoft 系统不允许使用冒号字符。)
5有一些索引标志——索引的缓存方面的一部分——可以滥用以防止git status
来自查找,git add
来自添加。这些是 assume-unchanged 和 skip-worktree 标志。它们不是为此目的而设计的,因此上面的 滥用 概念,它们对解决这个问题的特定问题没有帮助,但它们值得一提。
你必须做什么
您有多种选择。最激烈但最容易实施且可能最容易做到的是:创建一个全新的 Git 存储库。小心 never 添加这些文件,这样它们 never 就会被跟踪,因此永远不会成为问题。将所有系统移动到新的 Git 存储库,放弃(并最终销毁)旧的 Git 存储库。
或者,您可以进行最小更新:进行新提交,与旧提交相比,删除 文件。然后,转到 每个部署 并手动更新这些系统,小心地 保留 文件,同时使它们不受跟踪。您可以使用 git rm --cached
技巧,或者在结帐期间将文件保存在工作树之外,或者您喜欢的任何其他方式。这些方法中的任何一种都有效。然后 非常小心,永远不要 return 那些会使这些文件被跟踪的有毒提交。
在这两个选项之间,您可以使用历史重写工具(filter-branch、filter-repo、BFG,无论您喜欢什么)将现有提交转换为 new-and-improved 从未提交过这些文件的提交。这很像第一个 and/or 第三个选项:你仍然需要仔细地去每个部署并更新它,因为重写的存储库实际上是一个 new 存储库。它的缺点是,如果历史同步,拥有旧 (pre-rewrite) 存储库的人很容易意外 re-introduce 错误提交。 (他们是否这样做取决于前几次提交的内容 and/or 你如何重写历史记录。)
如果您拥有软件的完全控制权,最佳选项通常是这样的:
- 将 can/should 为 version-controlled 的重要数据(如果有)移动到新文件名。例如,这可能是
config.defaults
.
- 将不能版本控制的重要数据(因为它在每个站点上不同)移动到新文件名。例如,这可能是
config.site
.
- 确保
config.site
没有出现在任何过去、现在或将来的提交中。 做 将它列在 .gitignore
中,这样它就不会被意外添加和提交。
- 更新所有安装以获得正确的(根据定义,未跟踪的)
config.site
文件。
- 分发新修订版。所有站点现在都使用默认值和 per-site 配置。 None 的旧提交需要更改。
你无法改变过去,但也没有必要那样做。
我遇到这样一种情况,一些以前签入 Git 的文件现在需要忽略。为了忽略它们,我将文件添加到“.gitignore”并执行了以下操作:
git rm -r --cached .
git add --all
git commit -m "Removed files from git tracking that should be ignored"
git push
现在,我需要将这些“.git忽略”更改拉到另一台服务器,但是当我执行 git pull
时,刚刚添加到“.gitignore" 不会被忽略,而是被完全删除!
我认为在拉动过程中发生的事情是使用不忽略这些文件的本地“.gitignore”文件...并且它检测到这些文件不再在 git 所以它只是删除它们。如果我手动添加文件并执行另一个 git pull 然后它开始正常工作(现在服务器上有正确的“.gitignore”文件。)
有什么方法可以告诉 git pull
使用来自远程服务器的“.gitignore”文件而不是本地文件,这样这些文件就会被正确地忽略并且不会被获取在 git 拉动时移除?
在.gitignore
中列出一个文件并不意味着忽略该文件,也不意味着不要删除该文件,或任何您希望它表示的意思。你在这里做的任何事情都不会改变这一点。我们将在这个答案快结束时回到 .gitignore
,但让我们先看看你所处的可怕、可怕的 no-good 情况,你实际上无法 修复。您将不得不以某种方式绕过它。
这是错误的地方
事实是,一些现有的提交 有 这些文件,以及一些其他现有的提交——那些发生在这些文件存在之前的提交,以及那些你在获取这些文件之后所做的提交Git 的索引 out—没有这些文件。没有什么可以改变这些事实。
要理解为什么会这样,请注意 Git 不是 about 文件。 Git 大约 提交 。提交确实存储文件,但这是一揽子交易:全有或全无。你有一个提交,你有它的所有文件。或者,您没有提交,也没有它的文件(因此您将使用 git fetch
将提交添加到您的集合中,然后您拥有它 and 它的所有文件)。
此外,提交中的文件是无用的格式(我们将在下一节中回过头来)。它们被压缩并且 de-duplicated,因为大多数提交大多具有与其他提交中的 相同的 文件。所以 Git 不会将它们 存储为 文件,而是存储为内部 对象 ,它会自动 de-duplicates 它们。
这些对象都有编号,Git 称之为 散列 ID。特别是提交总是得到一个 unique 编号。 (文件,可能是其他提交文件的副本,可能有 non-unique 数字,这就是 de-duplicates 它们。)这个数字实际上是内部对象内容的加密哈希。这限制了 Git:甚至 Git 也不能 更改 提交。
如果您取出一个提交,进行一些更改,然后放回不同的东西,它就会不同,因此会得到一个新的不同的哈希 ID。现有对象保留在 Git 存储库中,位于其现有 ID 下。新的和改进的(我们希望)对象现在 添加 到存储库,在它的新 ID 下。任何使用 old ID 的人都会得到旧对象。使用 new ID 的任何人都会获得新对象。这部分真的很简单。
现在,提交中的数据不仅仅是每个文件的快照。是的,它就在那里,但也有一些 元数据 ,或关于提交本身的信息。例如,这包括提交人的姓名和电子邮件地址。它还包括一个 time-stamp——这有助于确保每个提交都是完全唯一的,因此如果两个不同的人进行 相同的 提交,但出于某种原因,他们都声称是同样的 person,他们仍然会得到不同的 commits 除非他们同时做出他们(在这种情况下,他们真的是两个不同的人?Git 说不)。
因此,每个提交中都有所有这些元数据:作者、提交者、一些 time-stamp、日志消息等。但是在这些元数据中,Git 添加了自己的信息。 Git 在每次提交时存储一组 较早 提交的哈希 ID。大多数提交只存储一个哈希 ID,Git 调用 父提交.
这些父提交哈希 ID 形成 backward-looking 链中的提交。我们从最近(最后)提交的右侧开始。我们不会写出它的真实哈希 ID,而是将此提交称为 H
(对于 H
ash):
<-H
提交 H
中同时包含快照(文件)和元数据,在该元数据中,提交 H
存储了较早提交的哈希 ID。我们假设它的hash ID是G
,画在:
<-G <-H
当然,提交 G
指向一个 still-earlier 提交,它一直向后指向:
... <-F <-G <-H
因为每个提交都指向其父项,Git 可以并且将会找到 整个提交链 如果 Git 只能找到 last 在链中提交。这就是分支名称的来源:Git 中的每个名称——分支名称、标签名称、remote-tracking 名称等等——存储一个哈希 ID。对于分支名称,该哈希 ID 是其链中 last 提交的 ID。即使链中有 still-later 次提交也是如此,这通常发生在开发新内容但尚未将其放在主分支上时:
...--G--H <-- main
\
I--J <-- feature
在这里,feature
分支比 main
分支多了两次提交。提交 J
指向 I
,后者指向 H
,后者指向 G
,依此类推。所以提交 H
和 earlir 在 两个 分支上。提交 I
和 J
目前仅在 feature
上,但如果我们愿意,我们可以“向前滑动名称 main
”:
...--G--H--I--J <-- main, feature
现在所有提交都在两个分支上。
分支名称四处移动,每个名称根据定义 挑选出最后 提交被视为“在那个分支上”。 提交自己 确定那些分支上的早期内容。所以重要的是 commits:名称只是让我们 find 特定的。而且,请记住,所有 提交都被永久冻结。任何现有提交的任何部分都不能更改。
签出提交
正如我们上面提到的,提交中的文件的格式只有 Git 可以使用它们。即便如此,Git 也只能读取。我们需要其他程序能够读取 和写入 到我们的文件。解决方案很简单——并且与其他版本控制系统使用的相同:Git 在某个时候从提交 中复制文件。这些副本一旦脱离版本控制系统,现在就很有用了。事实上,它们只是普通的普通文件:计算机上的任何东西 都可以使用它们。 Git 不再对它们有任何控制。
获取 Git 从某些提交中复制文件的正常日常方法是使用 git checkout
。例如,如果我们有:
...--G--H <-- main
\
I--J <-- feature
并且我们运行git checkout main
、Git将从提交H
中复制所有提交的文件。这也有 select 将名称 main
作为我们的 当前分支 的副作用。由于名称 main
指向提交 H
,这意味着 H
是我们的 当前提交 。我们可以通过将特殊名称 HEAD
附加到名称 main
:
...--G--H <-- main (HEAD)
\
I--J <-- feature
请注意,我们现在每个文件都有 两个副本:H
中有一个我们无法触及的已提交副本,还有一个 ordinary-form Git 称为我们的 工作树 或 work-tree.
在其他版本控制系统中,您只能找到这两个文件副本。1如果您想知道发生了什么,您可以比较 work-tree 一些文件的版本到活动提交的版本:不同的是我们已经改变的。但无论出于何种原因——无论你认为这是个好主意2——Git 存储每个文件的第三个副本3 在 Git 不同的地方,index 或 staging area,或者——最近很少——缓存.
每个文件的第三个副本位于 read-only 提交副本和 work-tree 副本之间。与提交的副本不同,它可以被覆盖。它是 pre-compressed 和 pre-de-duplicated,因此它已准备好进入 next 提交。事实上,这可能是考虑 Git 的索引 / staging-area 的最佳一般方式:它包含你的 提议的下一次提交。 4
所以,当你 git checkout
一些提交时,比如提交 H
,Git:
- 从提交中填写其索引,以便您建议的 next 提交匹配;
- 使用这些相同的文件来填充您的工作树,以便您可以查看和使用您的文件;和
- 将
HEAD
附加到分支名称,假设您使用main
之类的分支名称来 select 提交。 (如果没有,您将进入“分离的 HEAD”模式,我们不会在这里解决。)
如果您现在对文件的工作树副本进行更改,通常还必须 运行 git add
:这告诉 Git 使索引副本匹配工作树副本。对于您就地更新的文件,这会用新索引副本覆盖旧索引副本。对于您删除 的文件,此删除 索引副本。对于新文件,这会在 Git 的索引中创建一个新文件。
无论哪种方式,添加文件 stages 更改,因为任何时候你 运行 git commit
, Git 都会从那时索引中的任何内容。如果您没有更改索引,则新快照将与当前快照完全匹配。在这种情况下,Git一般要求你使用--allow-empty
标志:新提交实际上并不是空,只是它匹配旧的 snapshot-wise(所以Git想知道:为什么要麻烦?并让你使用标志)。
您是否对 work-tree and/or 运行 git add
进行任何更改以更新 Git 的索引 你的 work-tree, 当前提交 保持不变。一旦你做了一个新的提交,Git:
- 收集元数据;
- 写出快照ad metadata,得到一个hash ID作为结果;和
- 将哈希ID写入当前分支名称。
我们最终得到,例如:
K <-- main (HEAD)
/
...--G--H
\
I--J <-- feature
现在 main
上有一个不在 feature
上的提交。
1non-current 提交中的其他 read-only 副本也可以找到,就像它们在 Git 中一样,但是它们'重新 active 当前提交的方式。
2其他系统没有索引,证明没有索引也是可以的
3这个“副本”是pre-de-duplicated,所以大部分时候,几乎不用space。因此,将其称为 copy 有点误导。然而,与向用户显示的 Git 的许多其他位不同,这个“副本”自动 de-duplicated 的事实实际上是 well-hidden。您可以将其视为每个文件的第三个副本,一切正常。好吧,直到你开始摆弄像 git ls-files --stage
和 git update-index
这样的内部命令:然后你需要了解 git hash-object
.
4索引在冲突合并期间被扩展,这意味着此描述不完整,但至少不是错误。 :-) 索引还可以使 Git 运行得更快,这就是它具有旧名称 cache 的原因。这些天你经常在选项标志中看到这个名字,比如 git rm --cached
.
在具有不同文件的提交之间切换
假设在提交 H
和提交 I
之间,我们 删除了 一个文件。进一步说,我们把它放在一个新的分支 X:
git checkout main
git checkout -b X
git rm somefile
git commit -m 'remove a file'
提交H
有一个名为somefile
的文件并提交I
缺少一个文件名为 somefile
.
当我们 git checkout main
时,文件 somefile
必须 返回 。 Git 将其从提交 H
复制到 Git 的索引和我们的 work-tree,现在我们有了文件。
当我们 git checkout X
返回提交 I
时,文件 somefile
必须 离开 。 Git 将其从 Git 的索引 和我们的 work-tree.
这个属性是由两次提交中的文件集决定的。我会说完全,但是如果你稍微试验一下,你会发现 Git 对文件 somefile
的 删除 是有条件的:
git checkout main # file somefile comes back
git rm --cached somefile # take somefile out of Git's index
因为我们在这里使用了git rm --cached
,Git从它的索引中删除了somefile
,但是没有触及我们的work-tree副本.如果我们现在 运行:
git checkout X
—记住,提交I
,select由分支名称X
编辑,缺少文件somefile
—Git 不会 从我们的工作树中删除 somefile
。原因是在 git rm --cached
之后,文件 somefile
是 untracked.
未跟踪的文件
Git 中的未跟踪文件只是现在在您的工作树中的文件,但现在不在 Git 的索引中。 就是这样——这就是整个定义——但它有很多后果,包括 git commit
不会在新提交中包含未跟踪文件的事实,以及缺少 删除我们刚刚看到的。
因为你的工作树是你的,你可以随时在其中创建和销毁文件。
因为Git的索引是Git的,Git可以把文件放在那里——但我们知道什么时候它会做哪个东西:
当您
git add
一个文件时,Git 根据该文件在您的工作树中的样子添加或删除该文件。当您
git checkout
提交时,Git 添加或删除文件 to/from 基于这些文件是否在其他提交中的索引。当您 运行
git rm --cached
时,Git 按照指示从 Git 的索引中删除文件。这里没有涉及的其他情况包括
git merge
如何操纵Git的索引,git reset
和git restore
如何工作等等。
因此,在某种程度上,你 控制哪些文件在 Git 的索引中——但它们倾向于镜像提交。
索引和工作树特定于每个克隆
Git 对于索引和工作树是否包含在存储库中有点矛盾。具体来说,git init --bare
创建一个没有工作树的存储库,但这样的存储库仍然有一个索引。 (它可能不应该,但确实如此。)还有 git worktree add
命令,因为 Git 2.5,它添加了一对——工作树 和 索引——存储库。因此在任何给定的存储库中可以有多个索引和-work-tree 集。
很明显,git clone
不会 复制任何现有存储库的索引和 work-tree(无论该存储库中存在多少)存储库)。所以指数,或所有指数, 和 work-tree(s) 对每个克隆都是私有的。您无法直接控制任何其他存储库的索引和 work-tree:您必须将其留给可能在另一台机器上操作 Git 的任何人(假设另一个克隆在另一台机器上)。
关于.gitignore
.gitignore
文件命名错误。更好的名称是 .git-do-not-complain-about-these-files-if-they-are-untracked-and-if-they-are-untracked-and-I-use-an-en-masse-add-command-do-not-add-them-to-the-index-either
.
当我们运行 git status
、Git 抱怨未跟踪的文件时。它变得非常抱怨!这很烦人,因为 work-tree 是一个普通目录,而我们使用的软件种类,我们 运行 程序会创建大量构建工件 in 我们的 work-tree 们。这留下了大量未跟踪的文件。 git status
命令变得嘈杂,我们的工作效率直线下降。
为了让 git status
到 关闭 ____ ,我们可以在 .gitignore
文件中列出这些预期的构建产品。 这对这些文件现在是否在索引中没有影响。但是如果它们不在索引中——如果它们现在未被追踪——然后git status
不会抱怨他们。
当然,如果 git status
不抱怨,如果 git add .
也“工作正常”,不添加它们就太好了 .所以这是在 .gitignore
中列出文件的第二个主要影响:如果文件 没有 已经被跟踪——如果它不在索引中 now——而我们运行git add .
,我们希望Git不添加.
如果文件已经在索引中(被跟踪),将其列在 .gitignore
中对 git status
和 git add
没有影响:文件的状态 会检查,git add
会添加文件。5 所以对于already-tracked文件, .gitignore
无济于事。这就是文件名不正确的原因。但是更正确的名称将无法使用,所以 .gitignore
是。
在 .gitignore
中列出一个文件还有一个副作用:它赋予了 Git 对文件 clobber 的权限。这主要涉及检出 确实 包含文件的旧提交,而文件正在 untracked-and-ignored。结帐继续进行,现在您已取出 tracked 文件,而未跟踪的数据实际上已被销毁。所以 真实的 全名可能是 .git-about-some-files-that-may-be-untracked-and-what-to-do-if-they-are:do-not-complain-and-do-not-auto-add-but-do-feel-free-to-destroy-these-files
之类的。 (但许多 Microsoft 系统不允许使用冒号字符。)
5有一些索引标志——索引的缓存方面的一部分——可以滥用以防止git status
来自查找,git add
来自添加。这些是 assume-unchanged 和 skip-worktree 标志。它们不是为此目的而设计的,因此上面的 滥用 概念,它们对解决这个问题的特定问题没有帮助,但它们值得一提。
你必须做什么
您有多种选择。最激烈但最容易实施且可能最容易做到的是:创建一个全新的 Git 存储库。小心 never 添加这些文件,这样它们 never 就会被跟踪,因此永远不会成为问题。将所有系统移动到新的 Git 存储库,放弃(并最终销毁)旧的 Git 存储库。
或者,您可以进行最小更新:进行新提交,与旧提交相比,删除 文件。然后,转到 每个部署 并手动更新这些系统,小心地 保留 文件,同时使它们不受跟踪。您可以使用 git rm --cached
技巧,或者在结帐期间将文件保存在工作树之外,或者您喜欢的任何其他方式。这些方法中的任何一种都有效。然后 非常小心,永远不要 return 那些会使这些文件被跟踪的有毒提交。
在这两个选项之间,您可以使用历史重写工具(filter-branch、filter-repo、BFG,无论您喜欢什么)将现有提交转换为 new-and-improved 从未提交过这些文件的提交。这很像第一个 and/or 第三个选项:你仍然需要仔细地去每个部署并更新它,因为重写的存储库实际上是一个 new 存储库。它的缺点是,如果历史同步,拥有旧 (pre-rewrite) 存储库的人很容易意外 re-introduce 错误提交。 (他们是否这样做取决于前几次提交的内容 and/or 你如何重写历史记录。)
如果您拥有软件的完全控制权,最佳选项通常是这样的:
- 将 can/should 为 version-controlled 的重要数据(如果有)移动到新文件名。例如,这可能是
config.defaults
. - 将不能版本控制的重要数据(因为它在每个站点上不同)移动到新文件名。例如,这可能是
config.site
. - 确保
config.site
没有出现在任何过去、现在或将来的提交中。 做 将它列在.gitignore
中,这样它就不会被意外添加和提交。 - 更新所有安装以获得正确的(根据定义,未跟踪的)
config.site
文件。 - 分发新修订版。所有站点现在都使用默认值和 per-site 配置。 None 的旧提交需要更改。
你无法改变过去,但也没有必要那样做。