繁体   English   中英

将 Dev 分支中的文件与 Master 分支对应项取消关联,以便它们在合并后保持独立

[英]Disassociating Files in Dev Branch from Master Branch Counterparts So They Remain Separate After Merging

背景

我有两个(实际上是很多分支)已经分道扬镳,需要合并以生产流程。

dev 分支文件名与 master 分支相同。 dev 分支中的一些具有共同名称的文件“准备好”用于生产,而其他文件“未准备好”,因为它们需要重新工作和/或预计会产生令人讨厌的冲突。

对于名称相同的“未就绪”文件,我很想将它们与主对应文件分离,以便在分支合并后它们可以保持独立。 我曾尝试将开发分支文件名重命名为其他名称,例如git mv NewFile.txt DevFile.txt ,但是除了正常的文件内容合并行为之外,合并仅包含重命名。

核心问题

是否有某种方法可以解除 master 和 dev 分支中的文件的关联,以便文件在合并后保持独立? 理想情况下,同时还保留他们的历史?

更多详细信息

我试图在 github 的公共仓库中包含一个简化的工作示例。 所有 shell 命令添加/mv/merge 等都包含在 script.sh 中。 要克隆/查看,也许您可以使用git clone git@github.com:NedScandrett1/TestRepo.git访问示例

是否有某种方法可以解除 master 和 dev 分支中的文件的关联,以便文件在合并后保持独立?

不完全是。 但是,您可能能够“足够接近”。 见下文。

理想情况下,同时还保留他们的历史?

文件没有历史记录,在 Git 中。 在 Git 中,提交历史,如果你运行:

git log

你会看到所有的提交——所有的历史——从当前的提交开始并向后工作。

Git 总是像这样向后工作,因为提交本身在内部向后工作:

  • 每个提交都有每个文件的完整快照,以及一些元数据:关于提交本身的信息。

  • 快照存储 Git 在您或任何人制作快照时所知道的所有文件。 这些都不能更改:它们是提交的一部分,提交的编号——它的 hash ID——取决于这些文件内容。

  • 元数据告诉 Git 提交的人、时间和原因(他们的日志消息)。 在此提交元数据中,Git 包含一个列表(通常只有一个提交),其中包含先前提交的 hash ID。 这也永远无法更改:此特定提交的 hash ID,即它在您的 Git 数据库中“我们拥有的所有提交”的编号,由这些内容(数据和元数据)决定。

元数据负责这个向后连接的字符串。 如果我们绘制一组简单的单父提交,将它们的实际 hash ID 替换为我们自己的理智的一个大写字母名称,我们会得到如下所示的内容:

... <-F <-G <-H

其中H代表序列中最后一个提交的 hash ID(提交编号)。 (Side note: Git finds this hash ID in the branch name , which by definition holds the hash ID of the last commit in the chain. Updating the branch is done by sticking a new hash ID into the branch name. That hash ID, whatever它是,成为分支中的最后一次提交。)

我们——人类,也就是——喜欢提交视为一组更改。 为此,Git 必须使用两个快照。 例如,要查看H发生了什么变化,Git 将提取GH的快照,并比较它们。 对于相同的东西,Git 什么也没说; 无论有什么不同, Git 告诉我们如何更改G中的副本以匹配H中的副本。 这向我们展示了发生了什么变化。 但事实上,我们所拥有的只是快照。

正如我在括号旁注中所说,分支名称仅包含某个链中最后一次提交的 hash ID。 在某个链条中排在最后并不意味着它是最后一个。 例如,如果我们有:

...--G--H--I   <-- last

我们可以添加一个指向现有提交H名称:

...--G--H   <-- interesting
         \
          I   <-- last

现在,名为interesting的分支是在H结束的提交链,而名为last的分支是在I结束的提交链。 提交H最后一个分支last ,它不是最后一个分支last

考虑到这一点,我们可以回顾一下第一个问题:

我曾尝试将开发分支文件名重命名为其他名称,例如git mv NewFile.txt DevFile.txt ,但是除了正常的文件内容合并行为之外,合并只是简单地合并了重命名。 ...这样...文件在合并后保持独立...

当我们使用git merge时,这就是我们真正在做的事情:

          I--J   <-- br1 (HEAD)
         /
...--G--H
         \
          K--L   <-- br2

我们已经运行git checkout br1来选择提交J作为当前提交,通过分支名称br1作为当前分支 在这些图中,这就是br1所附的(HEAD)告诉我们的。 然后我们运行git merge br2

Git 现在:

  1. 使用(从J向后和从L向后的连接)来查找合并基础提交 这是两个分支上最好的共享提交。 通常只有一个最佳共享提交,在这个特定的图中,很明显:它是提交H 提交G也是共享的,但它不如H ,因为它“更靠后”。

  2. 运行一对git diff --find-renames命令(或其内部等效命令,真的)。 Git 需要找出自共享起点(在本例中为提交H )以来发生了什么变化,以分别在提交JL中生成快照。 这需要两个单独git diff命令。

    如果您重命名了某些文件,那么--find-renames部分可以解决这个问题。 所有 Git 都有快照 提交H有一些文件集,提交J有一些文件集,提交L有一些文件集。 如果提交HNewFile.txt但没有DevFile.txt ,并且提交LDevFile.txt但没有NewFile.txt ,那么,也许这些真的是“同一个文件”,不管这意味着什么。

    找出这种“相同性”(如果有的话)是--find-renames选项的工作。 使用git diff时,您可以控制查找重命名设置。 当你使用git mergegit merge控制它们; 但请继续阅读。

  3. 为了继续合并,Git 现在尝试合并在步骤 2 中发现的两组更改。这里有很多细节,我们将直接跳过。 :-) 假设 Git能够组合这些,Git 然后将组合更改应用于H中找到的文件。 会保留我们的更改br1中的H -vs- J ),同时添加它们的更改( br2 中的H br2 L )。

  4. 如果 Git 能够自行组合所有这些,它将 go on 进行新的合并提交,除非我们在git merge行上告诉它--no-commit-n 如果 Git 遇到合并冲突,它会停止并给我们留下一大堆需要清理的东西。 这就是我们在步骤 3 中跳过的那些细节变得重要的时候。 如果它停止 - 每个--no-commit或由于冲突 - 它仍然会记录正确的内容以在我们完成合并时进行新的合并提交

合并完成后,我们最终得到:

          I--J
         /    \
...--G--H      M   <-- br1 (HEAD)
         \    /
          K--L   <-- br2

提交M是一个合并提交,它的特点是它向后链接到提交J提交L

当 Git 在历史中倒退时,一次提交一个,它有一点问题,因为它现在必须同时倒退到两个提交。 无需详细说明,Git 在这里所做的一件事是它没有显示发生了什么变化,因为只有当我们选择一个单一的前提交到 go 和我们的一个后提交时,才显示更改的内容才有效。 对于合并提交,有两个(或更多)“之前”提交。 (存在两个以上父级的合并,称为octopus merges ,但我们不会在这里处理它们。)

(您可以在此处使用一些技巧,例如-m来“拆分”合并或--first-parent以避免查看合并提交的第一个父级以外的任何内容。我们也不会在这里介绍它们;它们是对以后合并错误的取证工作更有用,而不是现在实现良好的合并。)

一般重命名检测和合并

这种“检测重命名”操作对于以下方面很重要:

  • git diff ,如果你打开它;
  • git log --follow ,我们稍后会描述;
  • git merge ,你已经遇到过。

git merge运行它的两个git diff操作时,合并是控制重命名检测的操作。 但是git merge有命令行选项来告诉它如何控制它:

  • -X find-renames= number告诉 Git 使用什么 *rename 阈值;
  • -X no-renames完全禁用重命名检测。

(在非常旧的 Git 版本中, -X find-renames=...拼写为-X rename-threshold=...

启用重命名检测时,其默认值为50 这个值是一个百分比,尽管它的百分比到底多少有点不确定。 这意味着可能的值范围是从零到 100。然而,零实际上永远不会发生,并且 100 保留用于“文件内容完全匹配”,因此有用的值通常是 1 到 99。默认值50表示“50% 相似”。

每当 Git 比较两个文件时——我们称它们为 L 和 R,对于左侧和右侧文件——它可以计算“相似度指数”值。 这是基于观察 Git 在尝试确定 R 中保留 L 中的多少字节时所做的观察,以及 R 中的多少字节是全新的,或者需要从 L 中插入新的东西东西,什么的。 实际的计算是模糊的:它没有记录在任何地方。 但是,如果你自己运行git diff --find-renames ,并给它两次提交 hash ID,Git 会告诉你什么相似性索引was. 所以:

git diff --find-renames=01 --name-status L R

将比较提交 L 和提交 R,对于 R 中的每个新文件并从 L 中丢失,尝试猜测该文件是否被重命名。 使用此处的01值,它将接受低至“相似度指数 1%”(1% 相似度)的匹配并将其作为重命名。 然后--name-status选项使git diff仅打印出文件名和状态-es,并且R这意味着检测到重命名)将跟随实际相似性。

--diff-filter=R添加到我们的git diff使其打印R状态文件,以便我们查看每个检测到的重命名的实际相似性索引值是多少。

如果 Git没有检测到您希望它检测到的重命名,您可以尝试将git merge-X find-renames=25或更小的值合并。 请注意, 2表示 20%,而不是 2%; 你必须写022%来表示 2%。

如果 Git正在检测您希望它检测到的重命名,您可以尝试将您的git merge-X find-renames=75或更大的值,甚至-X no-renames renames 合并。

git log --follow

注意当使用git log --follow时,你必须给 Git 一个路径名:

git log --follow path/to/file.ext

Git 在这里所做的是从通常的反向、一次提交遍历一些提交图开始。 假设此时我们有这张图:

          I--J
         /    \
...--G--H      M--N--O   <-- somebranch (HEAD)
         \    /
          K--L

第一个比较是提交N与提交O Git 通过在内部运行git diff --find-renames NO来检查名为path/to/file.ext的文件是否被修改(打开了一些快捷方式以不打扰查看其他文件名)。 如果该文件的副本在NO中都相同,则 Git 将移回N而不显示提交O 如果文件在两个提交中都存在但不同,则 Git 显示提交O并移回N 如果文件在N中不存在,但从NO的差异可以找到rename ,则 Git 显示提交O并移回N ,但这一次,因为文件已从old/path/to/file.ext , Git 现在开始寻找该名称。

所以,如果我们从O移动到N ,我们现在正在寻找提交N的任何名称。 我们现在比较提交M ——一个合并提交:它仍然有一个快照,就像任何其他提交一样——提交N ,以查看文件是否更改和/或更改了名称。 如果它确实更改或更改名称(或两者), git log --follow显示提交M 如果没有,它不会显示M

在这里,因为M是一个合并提交,所以事情变得很棘手。 如果我们没有禁用Git 调用的历史简化功能,则 Git 现在会查看M的所有父项,以找到文件(无论此时其名称是什么)匹配任何父项。 如果它找到其中之一,Git 将 go 仅向下合并的那条腿 这是在行动的历史简化。

您可以使用--full-history来防止这种情况,但这与 `--find-renames 的交互作用很差,正如我们将看到的那样。

假设名称还没有改变,所以我们仍然有path/to/file.ext 这是我们在提交ONM中的文件名。 这也是提交L中的名称,但在提交K中,文件不同和/或具有不同的名称。

由于文件在L匹配,我们的git log --follow将从M走到L ,并将继续查看提交L ,然后K ,然后H ,然后G ,等等。

如果我们添加--full-history ,我们的git log --follow也会M走到J 但是,如果它已经从LK的分支,并且文件在LK的转换中被重命名,Git 将在JI提交中寻找错误的文件名

结论

这里要带走的是:

  • 没有文件历史记录之类的东西。 只有提交历史,因为提交就是历史。
  • 然而,Git 有一些工具——例如git log --follow可以通过选择提交历史的有趣部分来尝试生成一个合成的、减少的“文件历史”。
  • 这可以很好地工作,但它也可以 go 错误。 注意历史简化。
  • --follow今天做的--follow 不太好。 它依赖于重命名检测——这并不糟糕,但一开始也不是很好——并在此基础上添加了一些非常草率的假设,这使得它变得更加糟糕。 改进它会很困难(大约十年前我看过这个,从那时起我和其他任何人都没有修复它)。
  • 合并依赖于这个相同的重命名检测系统。

可能可以通过对重命名检测阈值大惊小怪获得有用的结果,但请注意,您可能希望对文件进行某种更改(即使只是添加一个大注释)以降低重命名相似性索引一点点。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM