Git:更改文件的“修改”属性

时间:2018-10-10 11:54:12

标签: git

我们的代码使用数据库,该数据库的特定模式版本与版本号相关联。假设当前版本为95。

开发人员的功能分支每次更改数据库架构时,都会创建一个相应的更新文件。现在,如果我在分支中更改架构,则将创建文件update_96。但是,如果其他人同时更新数据库,并且之前合并了他/他的功能分支以进行开发,则已经存在具有该名称的文件。合并或重新设置会产生冲突。

现在,两个新的update_96文件是独立的:我认为,我只需要将我的文件重命名为update_97,然后重新设置就可以了。但是git“知道”我的update_97update_96之前,并且无论如何尝试合并这两个文件。我想,原因是“修改”属性已重命名为“修改”。

如何将此属性更改为“已添加”?分别在单独的提交中删除/读取update_97的各种实验都不能解决问题。

2 个答案:

答案 0 :(得分:0)

您可以为这些文件设置-merge的git属性,这将完全阻止自动合并。

为此,您需要添加一行

update_* -merge

到您的.gitattributes

请参阅:https://git-scm.com/docs/gitattributes#_performing_a_three_way_merge

更出色的解​​决方案将定义并设置自定义合并驱动程序(也在上面的链接中进行了描述)

答案 1 :(得分:0)

Git没有文件属性。每个提交仅存储文件快照。如果提交A具有文件X,而提交B具有文件Y,则这是两个单独的提交,具有两个单独的文件。

当Git比较两个提交时,就像在其中Spot the Difference games中比较两个图片一样。左侧有快照,右侧有快照。每个快照均由文件组成。因此,让我们将提交A放在左侧,将提交B放在右侧,然后进行比较:

  • 如果提交A同时具有XY,并且提交B仅具有Y,则比较提交A与提交B的结果将是:文件X已删除(以及Y中的所有不同内容)。

  • 如果提交A仅具有X,而提交B同时具有XY,则比较A与B的结果将是: file { {1}}已创建(以及Y中的所有不同之处)。

  • 但是,如果提交A具有X(而不是X),并且提交B具有Y(而不是Y),则现在< / em>事情变得有趣。有一对名称不匹配的文件,但也许内容仍然匹配

这是第三种情况,Git将尝试猜测文件是否被重命名(也可能被修改)。如果内容完全相同(逐位匹配)100%相同,则Git将看到文件已重命名(否则保持不变)。但这对于实际情况还不够好,因此,如果内容仅匹配约99%或80%甚至51%的内容,Git也会 看到文件已重命名。

此重命名检测是确定文件身份的问题。 A中的文件X与B中的文件X是否相同?通常,这是一个非常棘手的问题(请参阅Ship of Theseus的哲学问题)。 Git有一个具体的答案:它会计算相似性索引,即介于0%相似和100%完全匹配之间的数字,并递减要重命名的文件 ,即同一文件-如果不匹配的左侧文件与不匹配的右侧文件至少相似50%。具有名称匹配的文件已经配对为“同一文件”,因此,仅适用于上述情况,文件YX的情况。

不过,这种“ 50%相似”只是默认的 。举例来说,如果您的迁移与其他人的迁移相似62%,而实际上应将 检测为“重命名”的所有其他文件更相似,则可以提高阈值< / em>。 (使用Y,您还可以完全关闭重命名检测。实际上,在旧版本的Git中,它默认为“关闭”,必须将其打开git diff。在Git 2.9中,默认设置为“ on”。)

现在,在上面,我一直在谈论-M,而您正在做git diffgit rebase。但是git merge实际上是一系列的Cherry-Pick操作,每个Cherry-Pick是一个合并,而git rebase当然也是一个合并(嗯,除了它是“快进” ”,这根本不是合并)。因此,您所做的一切都涉及Git的内部合并引擎。这意味着您需要了解 Git的合并引擎如何工作,这实际上非常简单:

  • 合并操作具有三个输入:合并库-我们称其为 B -左侧为 L 右侧 R 。左侧的 L 提交是git merge,而 R 的提交则是--ours一个。

  • 要执行合并,Git将合并基础与两个方面进行比较:

    --theirs

    Git然后将这两组更改组合为一个大更改,以应用于 B 。当两组更改在相同位置触摸相同文件时,就会发生合并冲突,这当然需要解决These修斯之船“其中哪些是相同文件”的哲学问题,但这就是上面git diff --find-renames <B> <L> # what we changed git diff --find-renames <B> <R> # what they changed 的目的。

    (当然,如果Git 找到了重命名,您可以进行重命名/重命名冲突,也可以重命名/删除冲突。但是您不必担心,直到它发生。如果这两个差异中只有一个“侧面”(一个B-vs-无论如何)重命名了文件,Git就会通过重命名来合并更改。)

  • 已合并更改,Git将其应用于 B 的内容以产生合并结果。如果一切顺利,Git可以直接提交结果。如果存在冲突,Git会尽力而为,将 conflicted 更改写入工作树,并以合并冲突停止(并将每个输入文件的所有三个版本保留在索引中) 。您可以清理烂摊子。

在您的特定情况下,出了问题的是,Git在您不希望重命名时正在检测重命名。如果两个文件逐位匹配100%,Git仍然会检测到这些重命名,但是在那种情况下,它通常是无害的。当Git猜错是因为文件有点太相似时,这种情况通常只是一个问题;反之,当文件不太相似时,情况就相反。

幸运的是,就像--find-renames一样,您可以控制相似度阈值。 git diffgit merge命令采用了Git所说的 strategy-option options ,我认为这是一个糟糕的名字。我称它们为扩展选项,因为它们的字母为git rebase(扩展名)。其中之一是-X。因此,如果重命名至少相似90%,而 bad 重命名仅相似62%,请执行以下操作:

-X find-renames=threshold

将阻止Git检测到“错误的”重命名。

当然,如果某些“好”重命名为51%,而某些“差”重命名为99%,则无济于事。在不知道实际数字是多少的情况下,您如何知道将git merge ... -X find-renames=90 ... 使用什么数字?答案是自己动手做-X find-renames

  • 查找合并库。对于git diff,请使用git merge。对于cherry-pick,合并基础是要复制的提交的父级。 Rebase使用cherry-pick,因此要复制的每个提交的合并基础是该提交的父级。找到发生“重命名”(实际上不是重命名)的提交对,并使用其父对。

  • 现在您有了合并基础,可以使用git merge-base --all将其与有趣的提交(合并的左侧和右侧)进行比较。 Git将告诉您检测到重命名的提交的相似性索引是什么。 (如果您使用的是较旧的Git版本,请确保使用git diff或将-M50设置为diff.renames,以便true进行重命名检测。)

    < / li>

此过程的输出将告诉您用于重命名阈值的数字。使用所有好的重命名都可以达到或超过的数字,但不好的数字则不会。

仍然存在一些问题。特别是,如果您要进行重新设置,那会进行一系列的樱桃小贴士。每次合并的git diff或左侧是在其上拾取cherry的提交,在复制完之前的提交之前,您不知道那是什么,因为rebase会构建 new 提交链,一次提交一次。也就是说,输入 L 提交是上一个樱桃选择的输出。 (这就是为什么某些基准库不断重复相同的合并冲突的原因。)

不过,这是使用Git提供的工具可以解决每个问题的通用方法。 (在实践中,您通常可以猜测:不正确的匹配表示默认值50%太容易接受,因此只需将一半严格设置为严格,或者75%再试一次即可。错过的匹配项意味着它过于严格,因此将一半设置为宽松,或25%,然后重试。)

(请注意,--ours是新的拼写;对于非常旧的Git版本,它是-X find-renames。请查看本地的-X rename-threshold文档。)