有什么办法可以删除包裹文件夹的跟踪,同时推送提交时不会导致从远程删除包?
No.
幸运的是,您可能不需要这样做。
不幸的是,无论你在这里做什么,使用起来都会有些丑陋和痛苦。
我已经阅读过有关假设不变和跳过工作树的内容...但我不确定使用哪个以及这些命令中的任何一个将在远程上产生什么效果(如果有)?
两者都可以,但是--skip-worktree
是你应该在这里使用的那个。两者都不会对任何其他 Git 存储库产生任何影响。
要理解所有这些,您需要一个关于 Git 实际功能的正确模型。
首先记住,Git 中的基本存储单位是commit。每个提交都有一个唯一的、又大又难看的哈希 ID,例如083378cc35c4dbcc607e4cdd24a5fca440163d17
。该哈希 ID 是提交的“真实名称”。各地的每个 Git 存储库都同意that哈希 ID 保留用于that提交,即使有问题的 Git 存储库没有have尚未提交。 (这就是 Git 中所有真正魔力的来源:这些看似随机但实际上完全不是随机的哈希 ID 的唯一性。)
提交存储的内容分为两部分:data,其中包含所有文件的快照;加上metadata,其中 Git 存储信息,例如谁进行了提交、何时(日期和时间戳)以及原因(日志消息)。作为元数据的重要部分,每次提交还存储一些数据集previous提交哈希 ID,作为文本中的原始哈希 ID。这让 Git 从任何给定的提交开始,向后,到之前的一些提交。
任何 Git 提交的实际哈希 ID 只是其所有数据的校验和。 (从技术上讲,它只是元数据的校验和,因为快照本身存储为一个单独的 Git 对象,其哈希 ID 进入提交对象。但是,这个单独对象的哈希 ID 也是一个校验和,因此通过数学计算默克尔树 https://en.wikipedia.org/wiki/Merkle_tree,一切顺利。)这是why提交中的所有内容都是完全只读的,并且永远冻结。如果您尝试更改提交中的任何内容,实际上您并没有change提交。相反,你会得到一个new提交,并使用新的不同的哈希 ID。旧的提交仍然存在,其哈希 ID 未更改。
所以:Git 就是关于提交的,Git 通过哈希 ID 查找提交。但我们人类无法处理哈希 ID(快说,这是 08337 之类的东西还是 03887 之类的东西?)。我们希望有names, like master
。同时,Git 希望有一种快速的方法来找到last在某些提交链中进行提交,并在某个时刻结束。所以 Git 为我们提供了名字,让我们可以创建分支机构名称.
分支名称仅保存分支的哈希 IDlast在某个链中提交。该承诺成立,因为它parent, 的哈希 IDprevious在链中提交。父提交作为其父项(我们最后一次提交的祖父母)保存了向前一步提交的哈希 ID,依此类推:
... <-F <-G <-H <-- master
如果提交哈希 ID 是单个字母,例如H
,这可能是一个准确的绘图:名字master
将保存哈希IDH
, 犯罪H
将保存哈希IDG
作为其父级,提交G
将保存哈希IDF
作为其父级,依此类推。
制作一个的行为new提交包括:
- 写出所有文件的快照;和
- 添加适当的元数据:您作为作者和提交者,“现在”作为日期和时间戳,等等。这parent这个新提交的内容应该是currentcommit 是,记录在当前分支名称中。如果
master
指着H
然后是新提交的父提交——我们称之为I
-将H
, 以便I points back to
H`.
实际进行此提交(并在此过程中找到其哈希 ID)后,Git 只需写入新的哈希 IDI
进入分支名称master
:
... <-F <-G <-H <-I <-- master
我们有一个新的承诺。
看什么happened在提交中,例如I
, Git 将提交(其所有文件)提取到临时区域,然后提取先前的提交H
的文件到临时区域,并比较它们。对于那些相同的东西,Git 没有说什么。对于那些不同的,Git 显示了差异。对于那些新的,Git 说它们是“添加”的,对于那些在上一次提交中但不在本次提交中的,git 说它们是“删除”。
现在,做一个git checkout
某些特定提交的意思是写入该提交的content—即,以您可以使用的形式输出数据。提交内永久冻结的文件副本采用仅限 Git 的格式,这对于存档来说很好,但对于完成新工作毫无用处。因此,Git 必须将提交提取到工作区,您可以在其中查看和使用文件。 Git 将此工作区称为您的工作树 or 工作树(或这些名称的某些变体)。除了当你要求时将文件写入其中之外,Git 基本上不干涉这个工作区域:那就是your游乐场,而不是 Git 的。
但是新提交中的新快照来自哪里?在某些版本控制系统中,新快照来自工作树中的文件。这是notGit 中的情况。相反,Git 从 Git 中的任何内容进行新的提交index。你不能see这些文件(至少不容易),但是当 Git 第一次提取某些提交时,它会有效地将所有该提交的已保存、冻结文件复制到 Git 的索引中。只有当它们进入索引后,Git 才会将它们复制(并解冻/重新水化)到您的工作树中,以便您可以使用它们。
The crucial difference between the frozen copies in a commit, and the "soft-frozen" copies in the index, is that you can overwrite the index copy.1 You can't overwrite the committed copy, but that's OK: commits cannot be changed, but you can make new and better commits, and that's what version control is about anyway.
每当你跑步时git commit
,Git 在第一步(制作快照)中所做的就是简单地打包所有预冻结的文件index每个文件的副本。所以我们可以将索引视为提议的下一次提交。这也是为什么你必须git add
文件始终存在,即使它们已经在上一次提交中。什么git add
正在做的是copying工作树文件位于该文件索引中的任何内容之上(尽管再次参见脚注 1 了解技术细节)。
这意味着什么每个文件始终有三个“实时”副本。一个被冻结在当前提交。一种是半冷冻的,在index,Git 也将其称为暂存区。最后一个是你的副本,在你的工作树中,你可以用它做任何你想做的事情:它是一个普通文件,而不是特殊的 Git-only 格式。
当你跑步时git status
,Git运行两个单独的比较:
First, git status
比较当前(HEAD
) 提交索引中的所有文件。对于每个文件the same,Git 什么也没说。对于每个文件不同的, Git 说这个文件是暂存以供提交。如果索引中的文件是新的 - 不在HEAD
——Git 称之为新的;如果一个文件是gone从索引来看,Git 说它是deleted.
Then, git status
将索引中的所有文件与工作树中的所有文件进行比较。对于每个文件the same,Git 什么也没说。对于每个文件不同的, Git 说这个文件是未暂存提交。如果工作树中的文件是新的——不在索引中——Git 会抱怨该文件是未追踪的。如果一个文件是goneGit 说它已从工作树中删除。
最后一个案例是在哪里未追踪的文件来自.它还为我们提供了未跟踪的定义:如果工作树中存在的文件不存在于索引中,则该文件未被跟踪。既然我们不能see索引,我们只看到这种情况git status
抱怨这些未跟踪的文件。
列出 a 中未跟踪的文件.gitignore
文件让 Git 闭嘴:git status
不会再发牢骚了这也使得git add
not add如果文件尚不存在,则将其添加到索引,但对已存在的文件没有影响are在索引中。如果文件位于索引中,则根据定义,它会被跟踪,并且git add
会很高兴地添加它。
终于到了这里--assume-unchanged
and --skip-worktree
进来。这些是您可以在文件上设置的标志are在索引中。设置任一标志都会告诉 Git:嘿,当您要考虑此文件的工作树副本时……您现在可以跳过它。那是,git add
查看索引和工作树,并检查.gitignore
文件,查看已跟踪的内容、未跟踪的内容、工作树中较新的内容以及需要在建议的下一次提交中更新的内容,等等。如果某个文件是未追踪的并列于.gitignore
, git add
会跳过它。如果它是tracked,如果工作树副本不同,Git 会添加它......unless跳过标志已设置。如果--assume-unchanged
标志已设置,Git 将assume它没有改变,也没有添加它。如果--skip-worktree
标志已设置,Git 知道它绝对不应该添加它,即使文件实际上已更改。
So --skip-worktree
这里的意思是我们想要的:don't git add
这个文件,即使它被改变了。 The --assume-unchanged
flag 也可以工作,因为 Git 假设它没有改变,因此不会git add
也可以。今天实际操作没有什么区别,但是“skip worktree”表达的是正确的intent.
请注意,因为这些标志是在index(又名暂存区)文件的副本,它们仅适用于tracked文件。跟踪的文件是索引/暂存区域中的文件。在设置标志之前,文件必须位于索引中。并且,如果该文件位于索引中,那个副本文件的内容(现在位于索引中的文件)就是将位于索引中的文件next你做出的承诺。
但是这个文件的副本是从哪里来的呢?答案就在我们的git checkout
早些时候:git checkout
将我们选择的提交中的所有文件复制到索引。它得到了into索引,然后进入我们的工作树,通过我们的第一个git checkout
。如果从那时起我们就对工作树副本大惊小怪,那么,flag我们设定的意思是git add
从未将工作树副本复制回索引副本,因此它仍然与旧提交相同。我们一直在使用old文件的副本,保存在索引中。
让这件事变得令人痛苦的是,如果我们git checkout
some other提交,另一个提交有一个不同的当我们获取其中文件的副本时,Git 将希望用我们尝试切换到的提交中的副本替换我们的索引副本。将其复制到索引不会删除我们设置的标志,但它will 覆盖工作树副本。如果我们更改了工作树副本,Git 会在不询问的情况下覆盖它(这可能很糟糕)或说:我无法检查该提交,它将覆盖您的(假设/跳过,但我不会提及)该文件的工作树副本。在实践中,Git 采用后一种方法。
为了解决这个问题,每次你git checkout
一个承诺would覆盖您标记的文件,您必须移动或复制您的工作树副本,let git checkout
覆盖索引和工作树副本,然后将工作树副本移动或复制回原位。显然,最好从一开始就不要陷入这种情况。
但是,如果你git rm
这些文件,某人会发生什么else谁从有文件的提交移动到没有文件的提交?例如,也许您正在推送的遥控器现在已签出该文件,然后他们就会git checkout
a new承诺你会做到这一点doesn't有那些文件。当然,他们的 Git 会尽职尽责地从theirGit 的索引,以及来自theirGit 用户的工作树。这就是你不想要的,所以现在你不得不保留their该文件的副本yourGit 的索引,以便它进入您的新提交。
这就是这个复杂的舞蹈的意义所在。每次提交都是一个快照在您的新提交中,您希望快照具有their某些特定文件的副本。所以你必须得到their复制到yourGit 的索引。您可以从某些提交中获取它,并将其复制到索引中。然后你把它固定在适当的位置,在yourGit 的索引/暂存区,即使您不在自己的工作树中使用它。在使用这三个副本时,您保留right复制(这不是您的工作树)到您自己的 Git 索引中。
1Technically, what's in the index is a reference to the frozen copy. Updating the index copy consists of making a new frozen copy, ready for commit, and writing the new reference into the index. These details matter if you start using git update-index
directly to put new files in, or use git ls-files --stage
to view the index: you'll see Git's internal blob object hash IDs here. But you can just think of the index as holding a full copy of each file, in the internal, frozen format: that mental model works well enough for the level at which you normally work with Git.