0

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

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

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

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


总而言之,我想要一个执行以下操作的解决方案:

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

1 回答 1

3

我想要一个可以做[Git 做不到的事情] 的解决方案

抱歉,但答案是:不,Git 不能那样做。 你可以靠近,但这并不有趣:它需要每个跑步者的努力git clone,从那时起,反复遭遇可能导致烧伤。这就是为什么标准方法是这个答案中推荐的方法我可以“git commit”一个文件并忽略它的内容更改吗?

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

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

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

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

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

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

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

这带来了这个新术语index,这就是麻烦开始的地方。

如果您的cfg/local/bar文件在您的索引中,它将在您的提交中。如果它不在您的索引中,则它不会在您的提交中。这很简单,但它的含义很糟糕:

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

  • 或者,您可以索引中的文件副本上设置--assume-unchanged或位。这几乎足够好,但还不够。(顺便说一句,这两者或多或少是等价的,但“skip worktree”是用于这种用途的——除了它的真正意图是用于稀疏结帐。我将在下面写“skip worktree”但是这实际上意味着任何一个。)--skip-worktree

设置该位要求您在git clone. 该索引对的存储库副本是私有的,因此运行的每个人也git clone必须运行此git update-index命令,至少一次,在git clone. (Git 不会让您通过 Git 本身自动执行此操作,当然您可以编写一个脚本来执行此操作并分发该脚本。)

正如您可能已经看到的那样,这只几乎有效。

  • 拉的内容~/.cfg/但不是~/.cfg/local/我时的内容git pull

再一次,Git 会在这里烧死你。问题在于它git pull本身并不是真正的事情:它意味着run git fetch,然后运行第二个 Git 命令,而第二个Git 命令会造成麻烦。

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

像提交一样,合并发生在索引中或通过索引发生。Git 将来自三个提交的所有文件加载到索引中,在两个单独的步骤中配对文件(基础与“我们的”,以及基础与他们的),然后组合配对。因此,这会合并索引中的每个文件,或者,如果先前提交中索引中的文件现在不在索引中,则删除或重命名文件。

这意味着如果一个文件cfg/local/bar存在于合并基础提交和“他们的”提交中——并且它需要在那里,如果你想要git clone填充cfg/local初始值cfg/local/bar——那么它也需要存在于“我们的”提交中,否则 Git 会坚持删除它以保留我们的更改。反过来,这意味着如果他们在提交中更改副本,Git 也会希望将他们的更改应用于您的提交中的副本。

如果您曾经git update-index--skip-worktree标志大惊小怪,那么您一直在重新提交原始版本cfg/local/bar。flag 只是告诉 Git:嘿,不要看我自己版本的这个文件,只是假设索引中的副本仍然正确。 这会影响git add -A步骤:而不是更新索引中列出的所有文件,它实际上是:更新所有没有特别标记的文件。 您可以随心所欲地更改cfg/local/bar,并且git add -A跳过更新:它不会将您的工作树复制cfg/local/bar回索引中,而是保留在您第一次为您git clone运行时将其放入索引中的副本git checkout

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

但是现在是合并时间,并且无论出于何种原因,他们已经改变了它们 cfg/local/bar——原因并不重要,重要的是他们确实改变了它——<em>现在你的 Git 面临着合并你的更改的工作(无)随着他们的变化(一些)。它通过进行唯一的更改来做到这一点——当然是他们的——现在你的 Git 会坚持将更新的内容复制cfg/local/bar到你的工作树中。这将覆盖您的cfg/local/bar,这就是痛点:这就是这种方法烧伤您的地方。

如果他们 从来没有(从来没有,一次都没有)改变他们的cfg/local/bar,这种方法——设置 skip-worktree——实际上会起作用。但这取决于陌生人的好意,或者至少取决于每次提交中的本地配置都cfg/local/bar完全相同的想法……在这种情况下,提交它的意义何在?

但是,如果他们确实更改了它,那么当您将他们的更改与您的缺少更改合并时,您会被烧毁(轻度或其他),因为 Git 会希望cfg/local/bar用他们更新的内容覆盖您。

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

唯一真正、100% 保证、正确的处理方法是:从一开始就不要提交文件。 如果存储库中的每个提交都没有cfg/local/bar,则该文件将永远不会放入索引中。如果该名称也列在 a.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/.cfg/local-committed/

于 2019-02-07T17:12:20.160 回答