繁体   English   中英

git 存储库中的 Composer 包冲突; 推送到远程时如何取消跟踪文件但避免删除文件

[英]Composer package conflict in git repository; how to untrack files but avoid deletion of files when pushing to remote

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

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

这个工作流程一切正常。

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

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

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

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

但这里的问题是,这将从服务器上删除软件包几秒钟,如果可能,我们希望避免这种情况,因为它是站点上的核心插件。 我们不想导致某些事情破裂。

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

我在这里阅读了关于assume-unchangedskip-worktree信息( Git - '假设未更改'和'跳过工作树'之间的区别),但我不确定使用哪个命令以及这些命令中的任何一个会产生什么效果(如果有的话)特别是在遥控器上?

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

不。

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

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

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

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


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

首先请记住,Git 中的基本存储单位是commit 每个提交都有一个唯一的、丑陋的哈希 ID,例如083378cc35c4dbcc607e4cdd24a5fca440163d17 该哈希 ID 是提交的“真实名称”。 每个Git仓库到处同意,哈希ID被保留用于提交,即使有问题的Git仓库没有犯下呢。 (这就是 Git 中所有真正魔法的来源:这些看似随机但实际上完全不是随机的哈希 ID 的唯一性。)

提交存储的内容分为两部分:数据,包含所有文件的快照; 加上metadata ,Git 存储信息,例如谁进行了提交、何时(日期和时间戳)以及为什么(日志消息)。 作为重要的元数据,每个提交还存储一些先前提交的哈希 ID,作为文本中的原始哈希 ID。 这让 Git 从任何给定的提交,向后,到以前的某个提交。

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

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

分支名称仅包含某个链中最后一次提交的哈希 ID。 该提交持有链中前一个提交的哈希 ID 作为其父项 父提交持有,作为它的父提交——我们最后一次提交的祖父——提交的哈希 ID 向前一步,依此类推:

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

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

进行提交的行为包括:

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

实际进行了这次提交(并在此过程中找到了它的哈希 ID)后,Git 只需将新的哈希 ID I写入分支名称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 会抱怨该文件未跟踪 如果文件从工作树中消失,Git 会说它已被删除。

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

.gitignore文件中列出未跟踪的文件会使 Git 闭嘴: git status不会再发牢骚了。 这也使得git add不能将文件添加到索引,如果它不是已经存在,但对那些在索引文件没有任何影响。 如果文件在索引中,根据定义,它会被跟踪,并且git add会很乐意添加它。

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

所以--skip-worktree意思是我们在这里想要的:不要git add这个文件,即使它被改变了。 --assume-unchanged标志也能工作,因为 Git 假定它没有改变,因此也不git add它。 今天实际操作没有区别,但是“跳过工作树”表达了正确的意图

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

但是这个文件的副本是从哪里来的呢? 答案就在我们之前的git checkoutgit 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 的级别来说已经足够好了。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM