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

Git - Have untracked files in online repository

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

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

我尝试关注 this answer,但这会从在线存储库中删除文件。

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


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

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

I would like a solution that does [things Git can't do]

抱歉,答案是:不行,Git不能那样做。你可以得到关闭,但这并不好玩:它需要 运行 和 git clone 的每个人的工作,并且从那时起就会反复遇到可能导致灼伤的情况。这就是为什么标准方法是 this answer to Can I 'git commit' a file and ignore its content changes?

中推荐的方法

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

  • keep the initial upload of the entire ~/.cfg/ (including ~/.cfg/local/) in the online repository.

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

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

  • push the contents of ~/.cfg/ but not the contents of ~/.cfg/local/ whenever I do the standard git add -A; git commit -m "asdf"; git push

这是第一个问题,那些 "mere" 语义至少有点烫伤:Git 不推送 文件 。 Git 推送 提交.

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

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

如果您的 cfg/local/bar 文件在您的索引中,它将在您的提交中。如果它 not 在你的索引中,它将 not 在你的提交中。就这么简单,但它的含义是令人讨厌的:

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

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

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

您可能已经看到,这仅 几乎 有效。

  • pull the contents of ~/.cfg/ but not the contents of ~/.cfg/local/ when I git pull

再一次,Git会在这里烧死你。问题是 git pull 并不是真正独立的东西:它意味着 运行 git fetch,然后是 运行 第二个 Git 命令second Git 命令会引起麻烦。

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

合并,如提交,发生在索引中或通过索引发生。 Git 加载 all 来自 three 的文件提交到索引中,在两个单独的步骤中配对文件(基本 vs "ours", 和 base vs theirs), 然后组合配对。因此,这会合并索引中的每个文件,或者,如果 在索引中 的文件在较早的提交中 不是 在索引中现在,删除或重命名 个文件。

这意味着如果文件 cfg/local/bar 存在于合并基础提交和 "their" 提交中——并且它需要存在,如果你想要一个初始的 git clonecfg/local/bar 填充 cfg/local——然后它也需要存在于 "ours" 提交中,否则 Git 将坚持 删除 它保留我们的零钱。反过来,这意味着如果他们在 他们的 提交中更改了 他们的 副本,Git 将希望将他们的更改应用到您的也复制你的提交。

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

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

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

If they never (not ever, not once) change their cfg/local/bar, 这种方法——设置skip-worktree —实际工作。但这取决于 st运行gers 的善意,或者至少取决于 cfg/local/barevery commit ever 中的本地配置完全相同的想法...在这种情况下,提交它的意义何在?

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

从您的索引中早期 删除 您的 cfg/local/bar 的替代方案更糟糕:现在每次提交 you push 根本没有文件。 Git 将其视为一个命令:当从一个有文件的提交转到没有文件的提交时,删除文件。 所以如果你采取这种方法,你是更改文件的人!您告诉其他人:删除此文件!

只有真正地,100% gua运行teed,处理这个问题的正确方法是:永远不要首先提交文件. 如果 每个 在存储库中提交 没有 cfg/local/bar,该文件将 永远不会 被放入索引。如果该名称也列在 .gitignore 中,则没有自动 "add all files" 会将其 添加 到索引中,因此它不会在 [=130= 中]未来 提交。这意味着它不会在您开始时在那里,也不会在您结束时在那里。 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/ 不存在204=]程序,程序通过从.cfg/local-committed/复制创建.cfg/local/。然后 "first setup" 的痛苦也消失了! 这就是为什么将默认配置提交到一个单独的文件中,用户手动或自动 复制 到本地配置文件,它永远是一个未跟踪的文件,是正确的解决方案。