Git - 防止在合并时添加开发目录

Git - prevent a development directory from being added on merge

我是一名程序员,更熟悉 C# 或 Python 等高级编程语言,但我最近开始深入研究 Git 的复杂性。上下文:我正在开发一个游戏,我有一个 development 分支,其中有一个特定的 'Development' 目录,为了保持 master干净。

我来到了 this answer,这几乎完成了工作;但是,此方法虽然不会 提交 对目录的更改,但 仍会在 master 分支 上创建它,这意味着每次合并后,我仍然需要返回并手动删除文件。

我正在设置一个别名 git publish 来自动执行此任务,为了完整起见,我将在此处 post:

[alias]
    publish = "!f(){ git checkout master \
        && git merge --no-commit --no-ff development \
        && git reset -- Assets/Development/ \
        && git commit \
        && git push; };f"

我想知道,有什么方法可以防止在 master 上创建 Assets/Development/

旁注:摆弄 .gitignore 无济于事,因为 .gitignore 的本质并不是真正的“要忽略的文件”,而是“抑制某些投诉的文件” , and/or 在某些情况下可以随意销毁”。 (这并不是说你不应该因为其他原因调整 .gitignore,只是说它对解决这个合并问题没有帮助,因为你真的 想要那些文件在开发分支中版本化。)

您现有的解决方法(以及可能改进它的方法)

一个技巧基本上就是您现在正在做的:进行合并(使用 --no-commit),然后删除不需要的内容。

剥离步骤是这样的:

git reset -- Assets/Development/

这使用 --mixed(默认,并且在给定路径时强制要求您)形式的 git reset,它设置(或“重置”,实际上)索引以匹配指定的树指定的路径。这里的路径是 Assests/Development/,即该子目录中的所有内容。指定的树,嗯,没有指定——所以它默认为 HEAD,或者更确切地说,HEAD 的树。 HEAD 是当前分支上的当前提交,当然是 master 因为我们刚刚检查过它;并且 master 根本不包含 Assets/Development/,因此重新设置的索引现在不再具有该子树。

简而言之,因为 Assets/Development/ 不在 HEAD 中,这会撤消 git mergeAssets/Development 中所有内容所做的 git add。问题是,虽然这会从 index 中删除条目,但会将它们留在 work tree.

有人可能会尝试 git reset --hard -- Assets/Development/ 但 git 不会那样做:您不能在提供路径时指定重置模式。不过,这还剩下三个更明显的选择:

  • 添加 rm -r Assets/Development(就在 git reset 之前或之后,甚至在 commit 步骤之后)

    这会删除文件,因此随着它们从索引和工作树中消失,您可以提交并完成。

  • 使用git rm -rf Assets/Development/(代替git reset

    这会删除索引条目并且会删除工作树文件(和目录),因此您可以提交并完成。

  • 提交后使用git clean。这可能比您想要的更多。

最主要的是,你可能只需要这样做一次,而不是重复。这取决于您在此过程中所做的更改。

关于合并的更多信息

当你运行 git merge <commit>, git 首先找到合并基.

合并基础被定义为L最低C共同A祖先(LCA ) 在图中两个节点之间的提交图中。这两个节点是您当前的提交——在本例中是分支 master 的尖端——以及您给 git merge 的提交,在本例中是分支 development 的尖端提交。 LCA 的定义需要一些图论,但粗略地说,它是 both 提交历史上的“最近”提交。

要了解这是如何工作的,绘制部分提交图会有所帮助。这里我们有 masterdevelopment 作为两个分支提示,两个分支结构(提交链)最终连接起来:

... <- o <- * <- o <- ... <- o     <-- master
             \
              o <- o <- ... <- o   <-- development

标记为 * 的提交是合并基础:它是 developmentmaster 重新加入的点,并且提交 * 并且每个较早的提交都在 两个个分支,而不是只在一个分支上。

此时 merge 所做的是生成两个差异:一个从 * 到当前提交的提示(master 上的提示提交),一个来自 * *给对方,目标,提交(development的提示)。

master 中你有一些(可能不是太多)自 * 以来的变化,在 development 中你有一堆变化,包括添加 Assets/Development/.

merge 命令合并了更改,其中包括添加不需要的目录。全部完成后,git 进行新的提交(--no-ff 步骤保证了这一点;如果合并基础提交 *master 的提示,这不是我们绘制的但是在第一次合并时是可能的,git 会尝试做一个标签快进而不是进行实际合并)。

结果是一个现在看起来更像这样的图表(我将停止绘制向左的箭头,请注意历史向左移动,即,后来的提交指向早期的):

...--o--*--o-...-o---o   <-- master
         \          /
          o--o-...-o     <-- development

现在您继续在 development 上开发,做出更多新的提交。让我们将 * 替换为 o,因为它不再是特例。

...--o--o--o-...-o---o       <-- master
         \          /
          o--o-...-o--o--o   <-- development

并且在某个时候你决定再次合并,所以你查看 master 并告诉 git merge 找到 development 的(新)尖端并计算合并基数。

合并基础再次(有点松散)是 两个 分支上的第一个提交。让我们找一下:它和以前一样吗?从 masterdevelopment 的尖端开始,从每个尖端开始,用两种不同的颜色为节点着色。一旦一个节点获得两种颜色,这就是一个共同的祖先。 最接近提示的是“最低”,即合并基础。

我不会画颜色,但是如果你自己做练习,你会看到是这个节点:

...--o--o--o-...-o---o       <-- master
         \          /
          o--o-...-*--o--o   <-- development

现在标记为 * 的节点可以从 master(通过跟随第二个父节点向下和向左)和从 development(通过跟随链向左)到达。它是最近的此类节点,因此它是合并基础。

Git 现在将合并基础 * 与两个提示进行比较。 master 的尖端应该几乎没有任何变化(基本上只有从前一个合并基础和新的 master 尖端之间的 o 节点拾取的任何东西),以及任何自上次合并以来您对 development 分支所做的更改。

如果这些更改影响 Assets/Development 中的现有文件,这些更改将导致合并冲突:

CONFLICT (modify/delete): Assets/Development/foo deleted in HEAD
 and modified in development. Version development of
 Assets/Development/foo left in tree.
Automatic merge failed; fix conflicts and then commit the result.

请注意,您不需要 --no-commit

如果这些更改在 Assets/Development/ 中添加 new 文件,另一方面,merge 会很乐意将它们添加到您即将进行的提交中,因此您可以这样做需要 --no-commit 来避免添加任何此类文件。

如果需要,您可以再次使用 git rm -rf 来丢弃任何冲突 and/or 添加的文件。