Git-在线存储库中有未跟踪的文件



我使用git(bitbucket)来源代码管理我的linux配置文件。所有文件都在目录~/.cfg/中。然后,我在~/.cfg/local/中还有一些本地配置文件,这些文件应该因机器而异。

我想在我的在线存储库中保留一份本地文件的副本,作为一种示例本地配置,但不想跟踪这些文件。我真的不在乎它们是否被git clone克隆,不管哪种方式都可以。

我试着遵循这个答案,但这会将文件从在线存储库中删除。

我也尝试了这篇博客文章中概述的解决方案,效果更好,但不幸的是有两个缺点:1)它必须在每台机器上重复,2)它实际上并没有取消对文件的跟踪。因此,如果我不小心从某台机器上传了本地配置(忘记从post中运行命令),那么任何其他机器上的下一个git pull都会覆盖该机器的本地配置。


总之,我想要一个能做到以下几点的解决方案:

  1. 它将整个~/.cfg/(包括~/.cfg/local/)的初始上载保存在在线存储库中
  2. 每当我执行标准git add -A; git commit -m "asdf"; git push时,它都会推送~/.cfg/的内容,但不会推送~/.cfg/local/的内容
  3. 当Igit pull时,它拉取CCD_

我想要一个能做Git不能做的事情的解决方案

很抱歉,但答案是:不,Git做不到你可以关闭,但这并不有趣:这需要每个运行git clone的人的努力,从那时起,反复的遭遇可能会导致烧伤。这就是为什么标准方法是这个答案中推荐给CanI';git提交';文件并忽略其内容更改?

这可能有助于理解为什么Git不能做到这一点。让我们更具体地看一下"那"是什么:

  • 将整个~/.cfg/(包括~/.cfg/local/)的初始上传保存在在线存储库中

这是可以做的。但措辞很奇怪,因为Git不存储文件。Git存储提交包含文件。这可能看起来只是语义,但话说回来,这只是"语义",关于"热水"是否适合淋浴(40˚C/104˚F:热,但不烫),或者会给你二度烧伤(95˚C/203˚F,在标准压力下接近沸腾)。

因此,可以有一个包含cfg/foocfg/local/bar在内的文件的提交。到目前为止,还没有真正的问题——主要的问题是,你不能有一个包含空目录cfg/local/的提交,因为Git在每次提交中只存储文件本身,而不是包含目录:它假设任何以后使用存储库的人都会根据需要自动创建目录,只要有文件要存储,其名称强制未来/其他Git调用CCD_ 19或创建包含该文件的目录的任何名称。

  • 每当我执行标准git add -A; git commit -m "asdf"; git push时,都会推送~/.cfg/的内容,而不是~/.cfg/local/的内容

第一个问题是,那些"纯粹"的语义至少有点烫人:Git不会推送文件。Git推送提交

这里有三个命令。第一个git add -A,告诉Git:更新索引中记录的所有文件的索引副本,将其替换为我的工作树中的新版本第二个git commit,告诉Git:使用存储在索引中的文件进行新的提交第三个,git push,告诉Git:向其他一些Git发送一些提交,然后要求其他Git设置一个或多个引用,例如其refs/heads/master-其master分支到一些hash-ID

这带来了这个新术语,索引,这就是问题的开始。

如果您的cfg/local/bar文件在索引中,那么它将在提交中。如果它在您的索引中不是,那么它将不会在您的提交中。这很简单,但它的含义很恶劣:

  • 您可以在不接触工作树版本(git rm --cached cfg/local/bar)的情况下从索引中删除文件,但这将导致将来的问题。

  • 或者,您可以在索引中的文件副本上设置--assume-unchanged--skip-worktree位。这几乎足够好了,但还不够。(顺便说一句,两者或多或少是等效的,但"跳过工作树"是用于这种用途的,只是它的真正目的是用于稀疏结账。我会在下面写"跳过工作树法",但这实际上意味着两者中的任何一个。)

设置位需要在git clone之后手动运行命令。该索引对于存储库的副本是私有的,因此每个运行git clone的人也必须在git clone之后运行此git update-index命令,至少一次。(Git不会让你通过Git本身来实现自动化,当然你可以写一个脚本来实现这一点并分发脚本。)

正如您可能已经看到的,这只是几乎工作。

  • 在Igit pull时提取~/.cfg/的内容,但不提取~/.cfg/local/的内容

再一次,Git会把你烧到这里。问题是git pull并不是它自己的东西:这意味着运行git fetch,然后运行第二个Git命令第二个Git命令会引起麻烦。

第二个Git命令通常是git merge,我们现在假设它是。另一个选项git rebase对你来说是更糟糕的,因为rebase本质上是重复的git cherry-pick,每个cherry-pick操作本身就是一个合并,导致多个合并。

与提交一样,合并发生在索引中或通过索引进行。Git将三个提交中的所有文件加载到索引中,分两个单独的步骤对文件进行配对(基本与"我们的"和基本与他们的),然后组合配对。因此,这会合并索引中的每个文件,或者,如果在早期提交时在索引中的文件现在不在索引中,会删除或重命名文件。

这意味着,如果文件cfg/local/bar存在于合并基提交和"他们的"提交中——并且它需要在那里,如果您希望初始git clonecfg/local/bar填充cfg/local——那么它也需要存在于"我们的"提交,否则Git将坚持删除以保留我们的更改。这反过来意味着,如果他们在提交中更改了

他们的如果您使用git update-index来处理--skip-worktree标志,那么您一直在重新提交cfg/local/bar的原始版本。标志只是告诉Git:嘿,不要看我自己版本的这个文件,只要假设索引中的副本仍然正确这会影响git add -A步骤:它不是更新索引中列出的所有文件,而是:升级所有未特别标记的文件您可以随心所欲地更改cfg/local/bargit add -A将跳过

更新:它不会将您的工作树cfg/local/bar复制回索引中,而是保留您第一次让git clone为您运行git checkout时放回索引中的副本。

因此,所有您的提交都有一个cfg/local/bar,但这些提交的内容存储在中,即每次提交中的cfg/local/bar与您运行git clone时获得的内容相同,即使您更改了工作树副本。您的skip工作树位告诉您的Git只保留cfg/local/bar的索引副本,它已经这样做了。

但现在是合并时间他们已经因为任何原因更改了cfg/local/bar——原因无关紧要,重要的是他们确实更改了它——现在你的Git面临着将你的更改(没有)与他们的更改(一些)相结合的工作。它通过采取唯一的更改(当然是他们的更改)来做到这一点,现在您的Git将坚持将更新后的cfg/local/bar复制到您的工作树中。这将覆盖您的cfg/local/bar,这就是痛点:这就是这种方法烧伤您的地方。

如果他们从未(从来没有,一次也没有)更改他们的cfg/local/bar,这种方法——设置跳过工作树——实际工作。但这取决于陌生人的善意,或者至少取决于每个提交cfg/local/bar中的本地配置完全相同的想法。。。在这种情况下,承诺到底有什么意义?

但是,如果他们真的改变了它,当你将他们的改变与你没有改变合并时,你会被烧伤(轻度或其他),因为Git会想用他们更新的cfg/local/bar覆盖你的CCD_ 66。

另一种选择是,在早期将cfg/local/bar从索引中删除,情况更糟:现在,您推送的每个提交都根本没有文件。Git将其视为一个命令:当从一个有文件的提交到一个没有文件的提交时,删除文件所以如果你采取这种方法,你就是更改文件的人!你告诉其他人:删除这个文件

只有真正的、100%有保证的、正确的处理方法是:一开始就不要提交文件如果存储库中的每个提交都没有cfg/local/bar,则该文件将永远不会被放入索引中。如果该名称也列在.gitignore中,则不会自动"添加所有文件"将其添加到索引中,因此它不会出现在未来的提交中。这意味着它不会在你开始的时候出现,也不会在你结束的时候出现。Git永远不会想合并它,也不会覆盖你的副本。它将始终是一个未被跟踪和忽略的文件,存在于你的工作树中,但不在你的任何提交中。

当然,这意味着最初会有一点痛苦:每次运行git clone <url>时,也必须执行:cp -r .cfg/local-committed/ .cfg/local。但是,如果要使用--skip-worktree,那么每次运行git clone <url>时,都必须立即使用git update-index --skip-worktree .cfg/local/bar。因此,这与糟糕的替代方案的痛苦程度完全相同,没有任何糟糕之处。

此外,如果您控制该软件,您可以设置该软件,以便在首次运行程序时,如果.cfg/local/不存在,则程序通过从.cfg/local-committed/复制来创建.cfg/local/。然后,"第一次设置"的痛苦也消失了这就是为什么将默认配置提交到一个单独的文件中是正确的解决方案,用户可以手动或自动将复制到本地配置文件中,而本地配置文件永远是一个未跟踪的文件

最新更新