我有 2 个存储库 (A) 和 (B)。(B) 是 (A) 的一个 Fork,并且已收到文件目录重命名。(B) 中的许多文件只是具有不同的父文件夹名称。现在我正在尝试将 (A) 的拉取请求发送到 (B) 但 Azure Repos 说目标文件已被删除。
有没有办法手动覆盖拉取请求,以便在 (A) 的文件映射到 (B) 的文件的地方发生从 (A) 到 (B) 的映射文件?同样,它们是相同的文件,只是具有不同的父文件夹。如果可以的话,我想避免更改 (A) 的文件夹结构。
我有 2 个存储库 (A) 和 (B)。(B) 是 (A) 的一个 Fork,并且已收到文件目录重命名。(B) 中的许多文件只是具有不同的父文件夹名称。现在我正在尝试将 (A) 的拉取请求发送到 (B) 但 Azure Repos 说目标文件已被删除。
有没有办法手动覆盖拉取请求,以便在 (A) 的文件映射到 (B) 的文件的地方发生从 (A) 到 (B) 的映射文件?同样,它们是相同的文件,只是具有不同的父文件夹。如果可以的话,我想避免更改 (A) 的文件夹结构。
简短的回答是“不”,但问题本身有点问题。如果你问对了问题,答案可能会变成“是”。
首先,“拉取请求”不是 Git——它是各种 Web 服务提供的附加组件,例如 GitHub 或 Bitbucket 或(在您的情况下)Azure。Git 真正拥有的是获取提交的能力——从其他 Git 存储库获取完整的提交——并合并.
当你获取别人的提交时,你得到的就是他们的提交。Universe 中的每个提交都有自己独特的、唯一的哈希 ID。哈希 ID 是提交中所有内容的加密校验和:快照中的所有文件、提交者的姓名和电子邮件地址、他们的日志消息,以及 - 对于 Git 至关重要 - 所有历史记录导致了这个时间点。也就是说,为了将此提交放入您的存储库,您还必须获取导致此提交的所有提交(包括它们的快照、作者和日志消息等)。
既然您的存储库中有他们的提交,那么您就有了他们的提交。现在由您决定您希望如何处理这些提交。您可以保持原样,也可以在复制过程中(在提交副本之前)复制它们并进行更改。这些副本可以有您喜欢的任何差异:请记住,副本将具有与原始不同的哈希 ID。只有原始提交可以使用原始哈希 ID。
如果您保留原件,则保留其文件结构。没有办法解决这个问题。具有唯一哈希 ID 的提交将永远被冻结。没有人——不是你,不是他们,不是 Git——可以改变那个提交。你要么拥有它,它就是那样,要么你根本没有它。(您可以通过在将这些提交填充到您的存储库之后决定您不喜欢它们来实现“根本没有它”状态。您只需停止使用它们并通过它们的哈希 ID 引用它们, 最终你的 Git 会丢弃它们。这里有一些关于引用和 reflogs 的技巧,但大多数情况下它只是拿走任何引用并等待的问题。)
如果您将这些原件复制到具有新文件结构的新提交中,那很好。无论您是否保留原件,您都可以保留您的副本。但是,您的副本只是——<em>你的——它们不会与他们未来的更新很好地结合起来,无论他们是谁。如果您打算与这些其他人进行持续的工作,那可能不是一条好路。
现在让我们看看第二个更有趣的部分:
有没有办法手动覆盖拉取请求,以便在 (A) 的文件映射到 (B) 的文件的地方发生从 (A) 到 (B) 的映射文件?同样,它们是相同的文件,只是具有不同的父文件夹。如果可以的话,我想避免更改 (A) 的文件夹结构。
现在我们知道 Git 中没有拉取请求这样的东西,我们可以把它变成正确的问题,即:
现在我的存储库中有他们的提交,我可以使用放松 Git 文件匹配规则的参数将他们的提交与我的提交合并吗?
这个问题的答案是肯定的。您可能需要使用命令行 Git 来执行此操作,而不是使用花哨的 Web 界面——例如,GitHub 的可点击按钮 Web 界面没有此功能。
当 Git 执行合并时(如 中所示git merge otherbranch
),此合并有三个输入。三个输入之一是您当前的提交——您所在分支的尖端,或HEAD
提交:这是同一个提交的两个名称,其真实名称是它的大而丑陋的哈希 ID。其中一个输入是您指定的另一个提交——在本例中为 <code>otherbranch,但您也可以只提供原始哈希 ID;出于合并的目的,Git 只是将名称 otherbranch
转换为原始哈希 ID。
这是两个输入,那么第三个是什么?答案是图形暗示了它。请记住上面我说过,如果您从其他人那里获取一个特定的提交,您还必须获取导致该特定提交的所有提交。我们可以用图形来绘制这种情况:
...--o--o--*--o--o--L <-- yourbranch (HEAD)
\
A--B--R <-- theirbranch (or theirrepo/branch or whatever)
这里L
代表您当前的(左或本地或)提交,并代表他们的(右或其他R或从R emote - Git 或获得)提交。 并且代表您需要从他们那里获得的提交的哈希 ID ,并且是您已经拥有的提交的父级的哈希 ID 。1--ours
R
--theirs
A
B
R
*
A
对于这些真正git merge
的合并案例——你的肯定是这样的——有效的方法是 Git 运行两个 git diff
s,以找出你改变了什么以及它们改变了什么。第一个差异实际上是:
git diff --find-renames <hash-of-*> <hash-of-L>
注意这个--find-renames
论点。第二个差异相当于:
git diff --find-renames <hash-of-*> <hash-of-R>
如果你没有重命名文件夹,在*
和之间L
,并且他们确实重命名了文件夹,在*
和之间L
,Git 将尝试在合并期间匹配文件,*
即使R
它们具有不同的名称。 这种尝试取决于文件内容的相似性。
同时,如果您*
在和之间重命名了一个文件夹,L
并且他们没有重命名该文件夹,Git 会执行完全相同的操作。它会尝试将基本名称*
与您在L
. 这种尝试取决于文件内容的相似性。
如果你们都重命名了文件夹,那也没关系。Git 尝试在 commit 中找到原始文件,*
基于其内容与两个分支提示中可能相同可能不同文件的每个新名称的内容相似性。
将所有重命名的文件配对*
并L
发现文件path/to/file.ext
in*
现在path/different/file.ext
在 中L
,Git 知道您所做的更改file.ext
是通过比较同一文件*
的原始名称和新名称file.ext
获得的更改。L
它还知道您重命名了该文件。*
类似地,将所有从to的重命名配对后R
,Git 知道它们所做的更改file.ext
是通过将*
' 的原始名称file.ext
与R
' 相同文件的新名称进行比较而获得的。
在所有情况下,一旦 Git 正确识别了重命名的文件,合并将照常进行:Git 尝试逐个文件合并您的更改及其更改。它还尝试保留你们中任何一个人所做的任何重命名。
这可能会以多种方式出错:
如果你们都重命名了file.ext
,Git 不知道要保留哪个新名称。您将遇到rename/rename
必须手动解决的冲突。这与您还必须自己解决的任何其他合并冲突是分开的。完成解析后,git mv
如有必要,文件给它一个您想要保留的名称,以及git add
您想要保留的名称下的组合更改。
如果更改文件名的人也过多地更改了内容,Git 将无法配对新旧文件。多少是太多了?好吧,Git 有相似度阈值的概念。当 Git 执行--find-renames
部分git diff old-commit new-commit
操作时,Git 将对似乎已从旧提交中删除的每个文件,将已删除文件的内容与似乎已在新提交中从头开始创建的每个文件的内容进行比较. 如果 oldfile.ext
与 new 的相似度为 30%,与 new 的相似度为 70%,则different.ext
70% 相似的other.ext
匹配获胜。但如果没有文件达到“50% 匹配”,则默认确定文件最终被删除。
如果你git diff --find-renames
自己跑,你可以添加一个重命名阈值因子,默认为 50%,但可以调整。根据需要向上或向下调整以使 Git 配对正确的文件。Git 会在它的 diff 输出中告诉你相似度指数是多少。
您可以在运行之前git diff
手动运行此类,并找到使 Git 匹配文件的适当相似性索引。然后,您可以运行以告知将该号码用于其两个操作。git merge
git merge -X find-renames=number
git merge
git diff --find-rename
当然,如果您必须将相似度阈值降低很多,那么合并操作本身很可能在这里发生冲突,因为这表明您已经对文件进行了太多更改,以至于他们所做的任何更改都可能会发生冲突与您所做的更改发生冲突。但这可能足以自动处理更多的合并。
因此,这里的配方是手动进行合并。首先用于git fetch
获取您要合并的提交。然后,使用git merge-base --all
查找将找到的共享合并基础提交git merge
。git diff --find-renames
使用此共享的合并库作为起点提交,并将您和/或他们的分支提示提交哈希 ID 或分支名称作为终点提交运行。添加--name-status
到此git diff
以获取哪些文件已配对并被发现已修改,哪些文件被视为已删除的摘要。调整重命名查找阈值(,或者如果您想使用短拼写),直到获得最佳结果。然后使用选项将相同的数字传递给两个底层差异。--find-renames=number
-Mnumber
git merge
-X rename-threshold=number
git merge
1有可能你已经拥有了A
,B
无论如何。提交*
的重要性在于它是最好的共享提交:在您的分支及其分支上的所有提交中,它是其中最好的。从技术上讲,它是构成存储库的提交的有向无环图 (DAG) 中两个选定提交的最低共同祖先 (LCA) 提交。您可以使用以下命令找到此提交的哈希 ID:
git merge-base --all HEAD otherbranch
例如。有时根本没有共同的提交,有时——很少——在 DAG 中有两个提交的不止一个 LCA,但通常这只会产生一个哈希 ID,这就是合并基础。