我想要一个可以做 [Git 做不到的事情] 的解决方案
抱歉,但答案是:不,Git 不能这样做。你可以得到close,但这并不有趣:它需要每个跑步者的努力git clone
,从此以后反复遭遇可能导致烧伤的情况。这就是为什么标准方法是推荐的方法这个答案 to 我可以“git commit”一个文件并忽略其内容更改吗?
它可能有助于理解whyGit 无法做到这一点。让我们更具体地看看“那”是什么:
- 保留整个初始上传
~/.cfg/
(包括~/.cfg/local/
)在在线存储库中。
是你can做。但这种措辞很奇怪,因为 Git 不存储files。 Git 商店commits, which contain文件。这可能看起来只是语义,但话又说回来,这只是关于“热水”是否适合淋浴(40°C / 104°F:热,但不烫伤)的“纯粹语义”,或者会给你第二个——度灼伤(95°C / 203°F:接近沸腾,标准压力)。
So, you can有一个包含以下文件的提交cfg/foo
and cfg/local/bar
。到目前为止,没有真正的问题 - 主要问题是你不能有一个包含空目录的提交cfg/local/
,因为 Git 在每次提交中只存储文件本身,而不存储包含的目录:它假设任何人using存储库稍后将根据需要自动创建目录,只要有一个文件要存储,其名称会强制将来/其他 Git 调用os.mkdir
或创建包含该文件的目录的任何内容。
- 推送内容
~/.cfg/
但不是的内容~/.cfg/local/
每当我执行标准时git add -A; git commit -m "asdf"; git push
这是第一个问题,那些“纯粹”的语义至少有点烫手:Git 不推送files。 Git 推送commits.
这里有三个命令。第一个,git add -A
,告诉 Git:通过将其替换为我的工作树中的新版本,更新索引中记录的所有文件的索引副本。第二个,git commit
,告诉 Git:使用存储在索引中的文件进行新的提交。第三,git push
,告诉 Git:将一些提交发送到其他某个 Git,然后要求其他 Git 设置其一个或多个引用,例如其refs/heads/master
—its master
分支——到某个哈希 ID。
这就引入了这个新术语,索引,这就是麻烦开始的地方。
If your cfg/local/bar
文件在您的索引中,它将在您的提交中。如果是not在你的索引中,它会not在你的承诺中。这很简单,但其含义却很糟糕:
You can remove索引中的文件而不触及工作树版本(git rm --cached cfg/local/bar
),但这会导致未来的问题。
或者,您可以设置--assume-unchanged
or --skip-worktree
文件副本上的位in你的索引。这几乎已经足够好了,但还不够。 (顺便说一句,两者或多或少是等价的,但“跳过工作树”就是专门用于这种用途的,只不过它的真正意图是用于稀疏结帐。我将在下面写“跳过工作树”,但是这实际上意味着其中之一。)
设置该位需要您在之后手动运行命令git clone
。该索引是私有的your存储库的副本,因此每个运行的人git clone
必须运行这个git update-index
命令,至少一个,紧随其后git clone
。 (Git 不会让您通过 Git 本身自动执行此操作,尽管您当然可以编写一个脚本来执行此操作并分发该脚本。)
正如您可能已经看到的,这只是almost works.
- 拉取内容
~/.cfg/
但不是的内容~/.cfg/local/
when I git pull
再一次,Git 会在这里烧死你。问题是git pull
它本身并不是一个真正的东西:它意味着run git fetch
,然后运行第二个 Git 命令和secondGit 命令会带来麻烦。
第二个 Git 命令通常是git merge
,我们现在假设是这样。另一种选择,git rebase
, is worse对你来说,因为 rebase 本质上是重复的git cherry-pick
每个挑选操作本身就是一个合并,导致multiple merges.
合并,就像提交一样,发生在索引中或通过索引发生。 Git 加载all文件来自three提交到索引中,在两个单独的步骤中将文件配对(基础与“我们的”,基础与他们的),然后组合配对。因此,这会合并索引中的每个文件,或者,如果文件was在早期提交的索引中isn't现在在索引中,删除或重命名 files.
这意味着如果一个文件cfg/local/bar
存在于合并基础提交和“他们的”提交中——如果你想要一个初始的,它就需要在那里git clone
填充cfg/local
with cfg/local/bar
——那么它也需要存在于“我们的”提交中,否则 Git 会坚持removing它可以保留我们的零钱。反过来,这意味着如果他们改变了their复制进去their提交时,Git 也会希望将其更改应用到您提交中的副本。
如果你用过git update-index
大惊小怪--skip-worktree
标志,您已经重新提交了原始版本cfg/local/bar
一直。该标志只是告诉 Git:嘿,不要看我自己的这个文件版本,就假设索引中的副本仍然正确。这会影响git add -A
步骤:代替更新索引中列出的所有文件,它实际上是:更新所有未特别标记的文件。你可以改变cfg/local/bar
你喜欢的一切,以及git add -A
will 省略更新:它不会复制您的工作树cfg/local/bar
返回到索引中,而不是将其放入索引中的副本保留在您第一次使用时git clone
run git checkout
为你。
所以所有的your提交有一个cfg/local/bar
,但是contents这些提交存储in that cfg/local/bar
,在每次提交中,都是你运行时得到的相同内容git clone
,即使您更改了工作树副本。你的skip-worktree位告诉你的Git只留下索引副本cfg/local/bar
独自一人,它已经做到了。
但现在既然是合并时间, and they已改变their cfg/local/bar
无论出于什么原因——原因并不重要,重要的是他们did更改-now你的 Git 面临着将你的更改(无)与他们的更改(一些)结合起来的工作。它通过采取唯一的改变(当然是他们的改变)来做到这一点now你的 Git 会坚持copying更新后的cfg/local/bar
进入你的工作树。这会覆盖你的cfg/local/bar
,这就是痛点:这就是这种方法让你感到痛苦的地方。
If they never(从来没有,一次也没有)改变他们的cfg/local/bar
,这种做法——设置skip-worktree——will实际上工作。但这取决于陌生人的善意,或者至少取决于本地配置的想法cfg/local/bar
in 每一次提交完全相同……在这种情况下,犯下这件事还有什么意义呢?
但是,如果他们真的改变了它,当你将他们的改变与你缺乏改变的东西合并时,你就会被烧伤(轻微的或其他的),因为 Git 会想要覆盖你的cfg/local/bar
与他们更新的一个。
另一种选择是,您remove your cfg/local/bar
从你早期的索引来看,情况更糟:现在每次提交you push 根本没有该文件。 Git 将此视为命令:从包含该文件的提交转到不包含该文件的提交时,请删除该文件。所以如果你采取这种方法,you're更改文件的人!你告诉其他所有人:删除这个文件!
The only确实,100% 有保证,正确的处理方法是:首先不要提交文件。 If every在存储库中提交doesn't have cfg/local/bar
,该文件将never被纳入索引。如果该名称列在.gitignore
同样,不会自动“添加所有文件”add它到索引,所以它不会在future承诺。这意味着当您开始或结束时它都不会在那里。 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/
当你第一次时不存在run程序,程序creates .cfg/local/
通过复制.cfg/local-committed/
。那么“第一次设置”的痛苦也消失了!这就是为什么将默认配置提交到一个单独的文件中,用户可以手动或自动copies到本地配置文件,它永远是一个未跟踪的文件,是正确的解决方案。