不受.gitignore_global文件影响的'git pull'的操作



我有两个本地 git 存储库,对应于一个名为quest的远程存储库。一个本地存储库位于Windows机器上,我通过Cygwin在其上运行git命令。另一个是在 Linux 系统上。

下面列出的具体示例是指后一种情况。

我的主目录 (/hhome/jj) 中有一个带有条目的.gitconfig文件

[core]
excludesfile = /hhome/jj/.gitignore_global

这指向一个全局.gitignore文件,也在我的主目录中。

我正在尝试设置 git 以忽略此 git 存储库的matlab_stuff子目录中的所有内容,现在和永远。此子目录的完整路径为/hhome/jj/gitroot/quest/matlab_stuff。我原以为我可以通过放置以下一行或另一行来实现这一点

matlab_stuff/
/hhome/jj/gitroot/quest/matlab_stuff/

~/.gitignore_global,但它不起作用

我知道这不足以让 git 忽略它已经在跟踪的文件。为了消除那些我使用了类似的东西

git update-index --skip-worktree <file-name>

matlab_stuff子目录中的每个文件上。然后我删除了整个/hhome/jj/gitroot/quest/matlab_stuff子目录。

一切似乎都很好。我可以对其他文件进行更改,提交它们并推送它们,而不会弄乱其他用户的全局存储库。

但是,一旦我在其他人将新文件推送到matlab_stuff后执行"git pull",该子目录就会重新创建并放入新文件,这正是我希望避免的。

运行git config --global -l会显示正确的core.excludesfile条目,因此 git 并不知道全局.gitignore文件。

正如我所说,这在两个类似配置的本地 git 存储库上无法按预期工作。

为什么这不起作用和/或我该如何解决它?

首先,git pull只是git fetch后跟第二个命令,通常是git merge(尽管你可以告诉它git rebase命令)。 所有真正的操作都是因为第二个命令而发生的:git fetch步骤是完全安全的,可以随时使用:它只是从其他 Git 获取提交并将它们添加到您的存储库中,您可以在其中对它们做任何您想做的事情以后。 它对任何现有的签出内容都没有影响。

接下来,我们可以指出.gitignore永远不会影响git merge(也不会影响git rebase)。 把这一点与第一点放在一起,你会发现.gitignore永远不会影响git pull.

提交、索引和工作树

但是,要了解到底发生了什么,我们必须在 Git 中了解这三件事之间的区别:

  • 提交。

    提交是 Git存在的理由。 每次提交都会保存源树的完整快照,以及一些元数据:作者(姓名、电子邮件和时间戳);提交者(相同的三件事,通常具有相同的值);一些提交或提交的哈希 ID,以便有提交的历史记录;和日志消息,通常由提交者编写(尽管合并提交通常具有自动生成的无聊消息)。

    提交中保存的快照由文件和子目录树(Git-ese 中的子树)组成,而子目录和子目录又包含更多文件和/或子子目录,依此类推。 每个文件都以压缩的内部格式存储,使用一个有趣的(散列)名称,这只对 Git 本身有意义。 因此,您还有...

  • 工作树。 工作树是 Git 允许你处理文件的地方。 在这里,它们具有计算机本身期望的普通格式,因此您可以编辑它们并更改它们等等。 通常每个存储库只有一个工作树。1

    这可能就足够了,其他版本控制系统实际上确实到此为止,但 Git 也为您提供了 - 并迫使您理解 - 另一部分,即......

  • 索引。 该索引起初非常神秘(即使对于核心 Git 用户,它仍然可能有点神秘)。 它还有其他几个名称:暂存区,这是一个相当好的名字——我们稍后会看到——还有缓存,它更多地是关于它的使用方式:索引的缓存方面使 Git 比许多其他版本控制系统具有巨大的速度优势。

    不过,关于索引的主要信息是,它是您构建下一次提交的地方。它开始"等于"一些现有的提交。 我们刚刚注意到,每次提交都是源的完整快照。 它到达那里的方式是从索引中保存出来。 当您首次克隆存储库并签出提交时,Git 会将提交复制到索引:因此您的索引与提交匹配。

    如果您所做的只是签出提交,则每个新git checkout都会使索引与新提交匹配。 您的索引和当前提交将始终匹配。 此外,如果您也从未接触过您的工作树,您的索引和工作树也将始终匹配。

    与工作树一样,通常只有一个索引。 索引记录有关工作树中的内容(这是其缓存方面)以及与提交的匹配的信息。 当然,您可以更改工作树中的内容。 完成后,您可以从工作树git add文件,以更新索引中的副本。 (如果它们是全新的路径名,则会创建新的索引条目。

    在更改工作树中的内容并git add(或git rm)所有内容后,您运行git commit进行新的提交。 这会将索引写出到某个树并写入一个提交,现在您的工作树和索引并提交再次匹配。

    请注意,git status进行了两个比较:

    1. 当前提交索引之间有什么不同?

    2. 索引和工作树之间有什么区别?

    前者是"暂存提交文件",后者是">暂存提交文件"。 这是因为如果你现在运行git commit,索引中的任何内容——当前提交中的所有原始文件,除了被你git add编辑的所有文件覆盖的文件——都将进入提交。 但是任何你没有git add的文件都不会进入新的提交:它们还没有暂存。 因此,索引是暂存区域。 您git add文件以从工作树中复制它们,使它们准备好提交。

现在,.gitignore文件根本不是关于提交,甚至不是关于索引的。 这里的问题是,在典型的工作树中,我们最终会得到不是源文件且不应提交的文件。 如果你的工作树中有这样的文件,并且你运行git status——你应该经常这样做——你会从 Git 那里得到关于所有这些未跟踪文件的各种抱怨。但是,究竟什么是"未跟踪的文件"?

Git 对未跟踪文件的定义非常简单(对于 Git):它是任何不在索引中的文件。 就是这样! 当前不在索引中的文件将被取消跟踪。 Git 会抱怨它——除非,也就是说,你把它列在.gitignore中。 跟踪索引中的文件,作为跟踪的文件,如果发生更改,Git 将自动添加该文件。我们稍后会看到assume-unchangedskip-worktree

这基本上是.gitignore为你所做的大部分工作:它关闭了Git关于那些未跟踪的文件。 它对跟踪的文件没有影响。 它们已经在索引中。 您无法阻止它们出现在索引中。 当然,您可以使用git rm将它们从索引中删除,这会将它们从索引工作树中删除。 这会设置一些事情,以便您进行的下一次提交将不再包含快照中的文件,因为这就是索引的用途:下一次提交


1 存储库是没有工作树的存储库。 它仍然有一个索引,但没有工作树,没有文件要不同步。 这意味着它可以很好地用作用户git push新提交的服务器。 这些新的提交不会惹恼任何积极工作的人:没有工作树,所以没有工作的地方,所以没有人在那里工作,也没有什么可搞砸的。

您可以使用 new-in-version-2.5git worktree子命令向 Git 存储库添加额外的工作树。 每个添加的工作树都有自己的索引。 仍然有一个使用"the"索引的"主"工作树,但由于每个添加的工作树都有自己的索引,因此您通常可以认为两者是捆绑在一起的。 但是,如果您对git --work-tree=太狡猾,这会崩溃:它继续使用主索引,因此您可以使索引与主工作树不同步。


假设不变和跳过工作树

这两个项目实际上是可以在索引条目上设置的特殊标志位。 由于它们是标志位,因此您必须具有某个文件路径的索引条目才能设置它们。 它们的目的有些不同,尽管它们在许多情况下都做同样的事情:

  • --assume-unchanged告诉 Git 不要费心检查文件的工作树版本是否比索引版本新。 这适用于"统计"操作非常慢或不需要的系统。 但是 Git 可能仍然会检查,如果出于其他原因必须这样做。

  • --skip-worktree告诉 Git,即使工作树版本肯定比索引版本更新,Git也应该假装不是这样。 这是"假设不变"的更强版本:Git 不仅可以跳过检查,它应该"闭上眼睛"并假装,即使它确实检查了

但是,两者的索引中仍包含该文件。 它们只是让你弄乱(即改变)工作树版本,而不会影响 Git 关于索引版本是否需要从工作树中重新加载的想法。签出和合并不遵守,甚至不使用这些索引标志位。

检出写入索引,然后写入工作树

我们在上面提到,每个新git checkout都会使索引与新提交匹配。 即使您设置了这些标志位,也是如此。 但是 Git 试图变得聪明和快速,它使用的技巧之一是:

  • 您有一个当前提交,它已被复制到索引中(然后复制到工作树中)。
  • 您正在签出一个新的(新近的)提交。 它的许多文件可能与旧的(当前当前的)提交完全相同。

如果新提交的README.txt副本与旧提交的副本完全相同,那么为什么,没有必要接触索引版本工作树版本。 其他 371,942 个未更改的文件也是如此;只有六个更改的文件必须复制到索引中,然后复制到工作树中。

如果某些、大部分甚至全部未更改的文件在索引中设置了一些特殊标志,并且工作树版本都乱了,那么工作树版本就会全部混乱。 这些更改可以方便地进行。

但是,如果某些更改的文件被弄乱了...好吧,Git检查:即使使用假设不变或跳过工作树,Git 也会查看是否必须删除文件。 如果是这样,它会告诉您您的文件将被破坏。 但是,如果它们只是从工作树中删除,则无需担心:没有宝贵的数据;将新文件提取到索引中,然后提取到工作树中是足够安全的。阿拉伯数字


2事实上,在.gitignore中列出这些文件名实际上使它们更容易被破坏,在某种程度上:一个未被忽略的未跟踪文件会让 Git 停下来说"嘿,我会覆盖或删除这个文件";但对于忽略的未跟踪文件,Git 可以随意覆盖或删除它。 这是在.gitignore中列出文件的方法之一,使文件更容易(而不是更少)容易受到意外更改的影响,尤其是在从具有或缺少该文件的提交移动到缺少或具有该文件的提交(分别

)时。

合并

当使用git merge时,图片会变得相当多云。 无论如何,合并(正如git merge所做的那样)有几种变体,其中两个在这里特别相关:

  • 快进合并实际上根本不是合并;它实际上只是伪装的git checkout(加上标签移动,git checkout -B)。
  • 真正的合并以更复杂的方式使用索引。

在不赘述细节的情况下,我们可以说,如果你从进行任何自己的提交,你的"上游"——你从中获取和合并的另一个 Git(又名"拉")——永远不会"重写历史",你将始终得到这些快进的非合并"合并"之一。 因此,这让我们回到了上面的git checkout案例。

怎么办

真正确保删除某些文件或整个子树并保持删除状态,您必须进行自己的提交。 理想情况下,您应该在自己的一个或多个分支上制作它们。 然后,您可以轻松、完全地控制"他们的工作"(您从上游存储库引入的提交)如何以及何时使用真正的合并与您自己的提交进行合并。

要删除子树并在您自己的分支上提交它,请执行以下操作:

git checkout -b newbranch

(这将在您当前提交时创建新分支);

git rm -r matlab_stuff

(这会将它们全部从索引和工作树中删除);以及:

git commit

最后一步在你刚刚创建的新分支上进行新的提交。 您有一个新提交,其中没有matlab_stuff目录满文件。 如果您创建任何此类文件,您现在可以忽略(如.gitignore)它们,或者如果您首先没有创建它们,则无需忽略它们。

请注意,将来,您最终会执行以下操作:

git fetch

并从"他们"(无论他们是谁)那里获取新的提交,然后您需要将他们的工作与您的工作合并

git merge origin/master

例如。

Git 现在将找到您最近同步的位置(此时,是您创建自己的分支的时间),并将所做的与他们所做的进行比较。 "你做了什么"很简单:你删除了所有这些文件。 "他们做了什么"——嗯,这取决于他们做了什么。

如果他们修改了一些matlab_stuff文件,Git 会给你一个合并冲突:">在他们的文件中更改,在我们的中删除。 Git 不知道在这里要做什么。不过,你这样做了:故意忽略了他们的工作,所以你想删除文件。 如果 Git 已经把他们的文件放回去(它会的),你现在的工作是重新删除它。 只需再次git rm即可。 这将告诉 Git,将其更改与删除相结合的正确方法是删除文件。 然后,您可以git commit结果,这将在您的分支上创建一个新的提交,记录最终的合并结果。

(请注意,所有这些冲突解决方法都使用索引。 每个文件实际上有四个插槽,其中最多三个实际使用过。 插槽 0 用于正常、无冲突的文件。 解决冲突涉及将结果复制到插槽 0。 插槽 1、2 和 3 用于在冲突合并期间保存合并基版本、--ours版本和--theirs版本。 作为解决合并问题的人,您的工作是查看这三个版本并提出正确的合并文件 — 或者在您的情况下,查看两个版本(基本版本和他们的版本)以及我们的缺少版本,并提出正确的缺少文件合并结果。

这不是世界上最方便的事情,但它是 Git 开箱即用的东西。 当然,您可以添加自己的包装脚本来自动删除任何合并冲突matlab_stuff文件。 这部分取决于你。

最新更新