首页 > 解决方案 > git 存储库中的 Composer 包冲突;如何在推送到远程时取消跟踪文件但避免删除文件

问题描述

我通过 Composer 在我的 Web 应用程序上安装了一个包。并将包文件夹添加到.gitignore,同时提交composer.jsoncomposer.lock

为了部署到我们的服务器,我们推送到服务器上的裸 Git 远程,然后将修改后的文件推送到服务器上的相关位置。

这个工作流程一切正常。

稍后,在存储库上工作的其他人将包文件添加到存储库并从 gitignore 中删除了该包。

我们希望包版本完全由 composer 管理,而不是像以前那样由 git 存储库管理。

到目前为止,我唯一的想法是执行以下操作:

  1. 从 repo 中删除文件并将包文件夹添加回 gitignore。提交这个。
  2. 推送到远程(这显然会推送已删除的文件)
  3. 推送后在服务器上快速运行composer update,重新安装删除的包。

但是这里的问题是这从服务器上删除包几秒钟,我们希望尽可能避免这种情况,因为它是网站上的核心插件。我们不想造成破坏。

有什么方法可以从跟踪中删除包文件夹,同时在推送提交时不会导致从远程删除包

我已经阅读了关于assume-unchangedskip-worktree这里(Git - 'assume-unchanged' 和 'skip-worktree' 之间的区别),但我不确定要使用哪个以及这些命令中的任何一个(如果有的话)会对遥控器产生什么影响?

标签: gitcomposer-phpgit-remote

解决方案


有什么方法可以从跟踪中删除包文件夹,同时在推送提交时不会导致从远程删除包

不。

幸运的是,您可能不需要。

不幸的是,无论你在这里做什么都会有点难看和使用起来很痛苦。

我已经阅读了有关假设不变和跳过工作树的信息……但我不确定要使用哪个以及这些命令中的任何一个会对遥控器产生什么影响(如果有的话)?

两者都可以,但--skip-worktree您应该在这里使用。两者都不会对任何其他 Git 存储库产生任何影响。


要理解所有这些,您需要一个 Git 实际工作的正确模型。

首先请记住,Git 中的基本存储单元是commit。每个提交都有一个唯一的、又大又丑的哈希 ID,例如083378cc35c4dbcc607e4cdd24a5fca440163d17. 该哈希 ID 是提交的“真实名称”。各地的每个 Git 存储库都同意为该提交保留哈希 ID ,即使所讨论的 Git 存储库还没有提交。(这就是 Git 中所有真正魔力的来源:这些看似随机但实际上完全非随机的哈希 ID 的唯一性。)

提交存储的内容分为两部分:数据,由所有文件的快照组成;加上元数据,Git 存储诸如谁提交、何时(日期和时间戳)以及为什么(日志消息)等信息。作为元数据的关键部分,每个提交还存储一些以前的提交哈希 ID,作为文本中的原始哈希 ID。这让 Git 从任何给定的提交(backwards)转到某个先前的提交。

任何 Git 提交的实际哈希 ID 只是其所有数据的校验和。(从技术上讲,它只是元数据的校验和,因为快照本身存储为单独的 Git 对象,其哈希 ID 进入提交对象。但是,这个单独对象的哈希 ID 也是校验和,因此通过默克尔树的数学,一切都解决了。)这就是为什么提交中的所有内容都是完全只读的,并且一直冻结。如果您尝试更改提交中的任何内容,您实际上并没有更改提交。相反,你会得到一个的提交,带有一个新的和不同的哈希 ID。旧的提交仍然存在,其哈希 ID 未更改。

所以:Git 是关于提交的,Git 通过它们的哈希 ID 找到提交。但是我们人类无法处理哈希 ID(快速,是 08337-something 还是 03887-something?)。我们希望有名字,比如master。同时,Git 想要一种快速的方法来找到在某个点结束的某个提交链中的最后一个提交。所以 Git 通过让我们创建分支名称来为我们提供名称

分支名称只是保存某个链中最后一次提交的哈希 ID。该提交作为其父提交持有链中前一个提交的哈希 ID 。父提交作为其父提交——我们最后一次提交的祖父提交——持有更远一步的提交的哈希 ID,依此类推:

... <-F <-G <-H   <-- master

如果提交哈希 ID 是单个字母,如H,这可能是一个准确的绘图:名称master将包含哈希 ID H,提交H将持有哈希 IDG作为其父级,提交G将持有哈希 IDF作为其父级,等等。

进行提交的行为包括:

  • 写出所有文件的快照;和
  • 添加适当的元数据:您作为作者和提交者,“现在”作为日期和时间戳,等等。这个新提交的级应该是当前提交的任何内容,如当前分支名称中记录的那样。如果master指向H则新提交的父级——我们称之为I——将是H,因此I points back toH`。

在实际进行了这个提交(并在过程中找到了它的哈希 ID)之后,Git 只需将新的哈希 IDI写入分支名称master

... <-F <-G <-H <-I   <-- master

我们有一个新的提交。

为了查看提交中发生I了什么,Git 将提交(其所有文件)提取到临时区域,然后将先前提交H的文件提取到临时区域,并进行比较。对于那些相同的,Git 什么也没说。对于那些不同的,Git 显示了差异。对于那些新的,Git 说它们被“添加”了,而对于那些在之前的提交中但不在这次提交中的,git 说它们被“删除”了。

现在,执行git checkout某个特定的提交意味着将该提交的内容(即数据)以您可以使用的形式写出来。提交中文件的永久冻结副本采用仅 Git 格式,这对于存档来说很好,但对于完成新工作却无用。因此,Git 必须将提交提取到一个工作区,您可以在其中查看和处理您的文件。Git 将此工作区称为您的工作树工作树(或这些名称的某些变体)。除了在您询问时将文件写入其中之外,Git 基本上不干涉这个工作区域:那是您的游乐场,而不是 Git 的。

但是,新提交中的新快照从何而来?在某些版本控制系统中,新快照来自工作树中的文件。在 Git 中情况并非如此。相反,Git 从 Git 的index中的任何内容进行新的提交。你看不到这些文件——至少,不容易——但是当 Git 第一次提取某个提交时,它会有效地将所有该提交保存的、冻结的文件复制到 Git 的索引中。仅当它们在索引中时,Git 才会将它们复制(并解冻/再水化)到您的工作树中,以便您可以使用它们。

提交中的冻结副本与索引中的“软冻结”副本之间的关键区别在于您可以覆盖索引副本。1 您不能覆盖已提交的副本,但这没关系:提交无法更改,但您可以进行新的更好的提交,这就是版本控制的意义所在。

每当您运行git commit时,Git 在第一步(制作快照)中所做的就是简单地打包每个文件的所有预冻结索引副本。因此我们可以将索引视为提议的下一次提交。这也是为什么你必须一直git add归档的原因,即使它们已经在之前的提交中。git add所做的是将工作树文件复制到该文件索引中的任何内容之上(尽管技术细节再次参见脚注 1)。

这意味着每个文件始终存在三个“实时”副本。一个被冻结在当前提交中。一个是半冻结的,在index中,Git 也称之为staging area。最后一个是您的副本,在您的工作树中,您可以随心所欲地使用它:它是一个普通文件,而不是特殊的仅 Git 格式。

当你运行 时git status,Git 运行两个单独的比较:

  • 首先,git status将当前 ( HEAD) 提交中的所有文件与索引中的所有文件进行比较。对于每个相同的文件,Git 什么也没说。对于每一个不同的文件,Git 都会说这个文件是为提交而暂存的。如果索引中的文件是新的——HEAD不在——Git 称它为新的;如果一个文件从索引中消失了,Git 说它被删除了。

  • 然后,git status将索引中的所有文件与工作树中的所有文件进行比较。对于每个相同的文件,Git 什么也没说。对于每一个不同的文件,Git 都说这个文件不是为提交而暂存的。如果工作树中的文件是新的——不在索引中——Git 会抱怨该文件是untracked。如果文件从工作树中消失,Git 会说它已被删除。

最后一种情况是未跟踪文件的来源。它还为我们提供了未跟踪的定义:如果工作树中存在的文件也不存在于索引中,则它是未跟踪的。由于我们看不到git status索引,我们只会在抱怨这些未跟踪的文件时看到这种情况。

在文件中列出未跟踪的.gitignore文件会使 Git 闭嘴:git status不会再发牢骚了。如果文件不存在,它git add也不会将文件添加到索引中,但它对索引中的文件没有影响。如果文件在索引中,根据定义,它会被跟踪,并且git add会很高兴地添加它。

最后,这就是进来的地方。--assume-unchanged这些--skip-worktree 是您可以在索引中的文件上设置的标志。设置任何一个标志都会告诉 Git:嘿,当你要考虑这个文件的工作树副本时......你现在可以跳过它。 也就是说,git add查看索引和工作树,并检查.gitignore文件,以查看跟踪的内容、未跟踪的内容、工作树中更新的内容以及在提议的下一次提交中需要更新的内容,等等。如果某些文件未被跟踪并列在 中.gitignoregit add将跳过它。如果它被跟踪,如果工作树副本不同,Git 将添加它......除非设置了跳过标志。如果--assume-unchanged如果设置了标志,Git 将假定它没有更改,并且不会添加它。如果--skip-worktree设置了标志,Git 知道它绝对不应该添加它,即使文件实际上已更改。

所以--skip-worktree意味着我们在这里想要的:不要git add这个文件,即使它已经改变了。--assume-unchanged标志也有效,因为 Git 假定它没有更改,因此也没有更改git add。今天实际操作并没有什么不同,但是“skip worktree”表达了正确的意图

请注意,由于这些标志设置在文件的索引(也称为暂存区域)副本上,因此它们仅适用于跟踪的文件。跟踪的文件是索引/暂存区域中的文件。该文件必须在索引中,然后才能设置标志。而且,如果该文件在索引中,那么该文件的副本——现在在索引中的那个——就是你下一次提交中的那个。

但是这个文件的副本是从哪里来的呢?答案在我们git checkout前面:git checkout将我们选择的提交中的所有文件复制到索引中。它进入索引,然后进入我们的工作树,由我们的第一个git checkout. 如果从那时起我们一直对工作树副本大惊小怪,那么我们设置的标志意味着git add从未将工作树副本复制回索引副本,因此它仍然与旧提交相同。我们一直在使用保存在索引中的文件的旧副本进行新的提交,可能是几天或几个月或其他任何时间。

让这件事很头疼的是,如果我们git checkout其他提交,而另一个提交中有不同的文件副本,Git 会想要用我们尝试的提交中的副本替换我们的索引副本切换到。将其复制到索引不会删除我们设置的标志,但 覆盖工作树副本。如果我们更改了工作树副本,Git 会在不询问的情况下覆盖它(这可能很糟糕),或者说:我无法检查那个提交,它会覆盖你的(假设/跳过,但我不会提到)该文件的工作树副本。 在实践中,Git 采用后一种方法。

为了解决这个问题,每次git checkout提交覆盖标记文件时,您都必须移动或复制工作树副本, git checkout覆盖索引和工作树副本,然后移动或复制您的工作树副本放回原位。显然最好一开始就不要陷入这种情况。

但是,如果你有git rm这些文件,那么从拥有文件的提交转移到没有文件的提交的其他人会发生什么?例如,也许您正在推送的遥控器现在已签出该文件,然后他们将进行您所做的没有这些文件git checkout提交。当然,他们的 Git 会尽职尽责地从他们的Git 索引和他们的Git 用户的工作树中删除这些文件。那是你不想要的,所以现在你被困在你的Git 索引中保留他们的那个文件的副本,以便它进入你的新提交。

这就是这个复杂的舞蹈的全部意义所在。 每个提交都是一个快照,在您的新提交中,您希望您的快照拥有某些特定文件副本。所以你必须把他们的副本放到你的Git 索引中。你从一些提交中得到它,将它复制到你的索引中。然后将其保留在Git 的索引/暂存区域中,即使您没有在自己的工作树中使用它在使用这三个副本时,您将正确的副本(不是您的工作树副本)保存在您自己的 Git 索引中。


1从技术上讲,索引中的内容是对冻结副本的引用。更新索引副本包括制作一个的冻结副本,准备好提交,并将新引用写入索引。git update-index如果您开始直接使用来放入新文件或用于查看索引,这些细节很重要git ls-files --stage:您将在此处看到 Git 的内部blob对象哈希 ID。但是您可以将索引视为以内部冻结格式保存每个文件的完整副本:该心智模型对于您通常使用 Git 的级别来说已经足够好了。


推荐阅读