将文件从一个存储库移动到另一个存储库并并行更改

Posted

技术标签:

【中文标题】将文件从一个存储库移动到另一个存储库并并行更改【英文标题】:Moving file from one repo to another and changing it in parallel 【发布时间】:2022-01-11 16:40:12 【问题描述】:

我有两个存储库:AB。文件 doc.txt 位于 A_master 分支的 A 存储库中。当前的 B 分支是 B_master

我在 A 存储库中创建了一个基于 A_master 的分支。我称之为A_feature。我还基于 B 存储库中的 B_master 创建了一个分支,B_feature。我在 A_feature 中提交了 doc.txt 删除。然后我在 B_feature 中添加了相同的 doc.txt。因此,将 doc.txt 从 A 存储库移动到 *_feature 分支中的 B 存储库。之后,有人更改了 A_master 中的 doc.txt。我要合并 A_feature 和 B_feature。

    我会丢失在 A_master 中所做的 doc.txt 更改吗?为什么? 我会在 *_feature 拉取请求中看到冲突吗?为什么?

编辑 1

...我要合并A_master中的A_feature和B_master中的B_feature。

【问题讨论】:

【参考方案1】:

Git repository 的核心是 commits 的集合。这与文件无关(尽管提交包含文件)。这与分支无关(尽管分支名称对我们有帮助,而 Git,find 提交)。它实际上只是提交的集合。

一个提交,在 Git 中:

是只读的。任何提交都不能更改!没有一点可以改变。

有编号,用hexadecimal 表示的大丑散列ID。该编号对于该一次提交是唯一的:在 any Git 存储库中的任何其他提交都不能有 那个 编号。 确实使用该编号的任何其他 Git 存储库都使用它来保存 该提交的副本。

包含所有文件的快照(采用特殊的、压缩的、仅 Git 的、去重的格式)和一些元数据。

Git 中的 分支名称 仅保存一个特定提交的哈希 ID(提交编号):该分支的最新提交。实际上就是这样:一个提交的名称。当您在该分支进行新提交时,Git 会自动将存储的哈希 ID 替换为最新的。

(每个提交中的元数据将它们连接在一起,因此,从最新的开始,Git 可以向后工作到每个先前的提交。因此保留最新提交的哈希 ID 就足够了。)

check out 提交的行为会导致 Git 提取所有保存在该提交中的 文件。提交中保存的文件无法更改,只有 Git 本身能够读取它们,因此我们必须提取它们才能使用它们。一旦 Git 中提取出来,这些文件就不再在 Git 中了。也就是说,您看到和使用的文件可能来自 Git 存储库和提交,但现在它们只是普通文件。

现在你知道了上面的内容,你可以看看你的描述中哪里有错误:

[I] 将 doc.txtA 存储库移至 B 存储库 ...

将文件从一个存储库移动到另一个存储库实际上是不可能的:

存储库不保存文件;他们持有提交。 无法更改提交。 “移动”文件意味着它从一个位置消失,现在出现在另一个位置。因此,这需要更改某些提交中的文件,这是不可能的。

您可以复制从某个 A 存储库提交中提取的文件到 B* 存储库的工作树中,使用 git add 准备它进入 B 中的新提交,并在 B 中运行 git commit 以添加新提交到 B 文件所在的位置。

您可以从 A 中的工作树中删除该文件并添加删除(git add 已删除的文件,或使用 git rm 完成整个操作一次),然后在 A 中进行新的提交,以将新的提交添加到文件不存在的 A 中。该文件继续存在于 A 中的先前提交中。

之后,[我做出并承诺]有人将doc.txt 更改为A_master

这意味着您将doc.txt复制B的工作树中,而不是“移动”(复制然后删除)doc.txt。您在存储库 A 中所做的新附加提交包含 doc.txt 的更新版本。先前存在的提交继续保留旧版本。

我将合并A_featureB_feature ...

这可能很困难:git merge一个存储库中的提交进行操作。您有两个不同的存储库,AB。如果它们包含相同的 starting 提交——请记住,Git 是关于 commits 的,正如它们的提交编号所发现的那样——你可能能够加载 current-private-to -A 提交到存储库 B,或者 B 提交到 A,然后你就可以运行了git merge 在这些提交上。

请注意,虽然 git merge 采用分支名称:

git checkout br1       # or git switch br1
git merge br2

这些操作基本上是关于存储库中的提交。合并操作 git merge br2 使用 name br2 来查找该分支的最近提交。然后,它使用来自当前提交的提交元数据、指定的提交以及任何需要的前置提交,来定位共同的起始点提交——合并基础——两个分支提示都来自该提交点下降。

如果提交不在同一个存储库中,则首先不可能合并它们。


按“编辑 1”编辑

...我要合并A_master中的A_feature和B_master中的B_feature。

现在让我扩展我自己的括号评论:

(每个提交中的元数据将它们连接在一起,因此,从最新的开始,Git 可以向后工作到每个先前的提交。因此保留最新提交的哈希 ID 就足够了。)

更具体地说,提交中的元数据包括其前一个提交的原始哈希 ID。因此我们说一个提交指向它的,我们可以这样画:

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

此处的分支名称 somebranch 用于保存此链中最后一次提交的哈希 ID H。提交H 然后保存快照(所有文件,每个文件都被压缩并针对此或任何其他提交中的文件的任何其他副本进行重复数据删除)和元数据; H 中的元数据包含早期提交 G 的哈希 ID。 commitG,作为commit,持有快照和元数据,其元数据持有早先commitF的hash ID,以此类推。

当您 git checkoutgit switch 按名称访问某个分支时,您会检查分支名称指向的提交。例如,如果您有:

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

然后你运行:

git switch master

Git 将从提交 H 中提取所有文件的快照。

当您更新一些文件和git add 和/或使用git rm,然后运行git commit,Git 将使用更新和添加和/或删除添加一个新提交文件。这个新的提交有一个完整的快照(基于你git add-ed,加上你没有更改或删除的任何文件)。它向后指向当前提交的

...--F--G--H   <-- does anything point here now? (commit I does)
            \
             I   <-- how about here?

棘手的一点是,无论分支名称是您的当前分支,根据git checkoutgit switch,Git 现在都会将I 的哈希ID 写入该分支名称

...--F--G--H--I   <-- master

旁注:此时,Git 会根据 Git 的 index暂存区 中的任何内容制作这个新提交的快照。 (索引和暂存区域是单个 Git 事物的两个术语。)使用git add 修改索引/暂存区域,以便为下一次提交做准备。您在工作树中看到和使用的文件是为 提供的,而不是为 Git 本身提供的:Git 使用存储在其索引中的文件来工作。 git add 命令是对 Git 说的一种方式:使某些文件的索引副本与这些文件的工作树副本匹配。

为什么这很重要

在 repo A 你现在有两个分支名称:

...--F--G--H   <-- master, A_feature

您选择其中一个作为git checkout A_feature当前分支。为了记住哪个当前分支,我们将特殊名称HEAD 添加到我们的绘图中:

...--F--G--H   <-- master, A_feature (HEAD)

现在您对某些文件进行更改,git add 如果需要(git rm 更改您的工作树 Git 的索引,因此没有单独的 @ 987654371@ 是必需的),并提交:

...--F--G--H   <-- master
            \
             I   <-- A_feature (HEAD)

您所做的更改是删除doc.txt,因此新提交I 中的快照比提交H 中的快照少一个文件。

当您进行更多更改并提交它们时,您会获得更多提交:

...--F--G--H   <-- master
            \
             I--J   <-- A_feature (HEAD)

您提到对这个存储库有写访问权的其他人(无论可能是谁,但可能会发生这种情况)现在执行git checkout master

...--F--G--H   <-- master (HEAD)
            \
             I--J   <-- A_feature

他们现在修改doc.txt,使用git add,并运行git commit

             K   <-- master (HEAD)
            /
...--F--G--H
            \
             I--J   <-- A_feature

提交K 与提交H 具有相同的文件,只是它的doc.txt 副本不同

如果他们再次提交,我们得到:

             K--L   <-- master (HEAD)
            /
...--F--G--H
            \
             I--J   <-- A_feature

我要合并 A_master 中的 A_feature 和 B_master 中的 B_feature。

因此,您现在将使用此存储库,将HEAD 附加到master,如下所示,然后运行:

git merge A_feature

Git 中的合并操作会找到两个要开始的提交:

    你当前的提交L(通过HEAD然后master); 另一个提交J(通过参数A_feature)。

然后它使用我们一直在绘制的 graph 来查找 both 分支上的最佳共享提交。在这张图中,就是提交H

现在合并开始真正的工作:

合并必须H 中的快照与K 中的快照进行比较,以查看您在当前分支上所做的更改。根据您的描述,更改的是或包括名为doc.txt 的文件中的数据。

合并必须H 中的快照与L 中的快照进行比较,以查看它们(无论他们是谁——实际上是你)在另一个分支上发生了什么变化。根据您的描述,更改是或包括删除名为doc.txt 的文件。

合并操作现在必须合并更改

在一个文件中合并更改的通常规则很简单,并且完全基于文本行。但在这种情况下,您没有更改 H-to-J 差异中的任何 。相反,您删除了整个文件。这是“高级”或“树级”操作。同时,他们确实更改了您删除的同一文件中的某些行。

Git 无法结合这两个更改。它没有解决这个问题的规则(即使是-X ours-X theirs)。您将遇到合并冲突。发生这种情况时,Git 会将其索引/暂存区域置于扩展的“冲突”状态。 Git 中途停止合并,并以失败状态退出git merge 命令,表示出现问题。

您现在的工作是修复问题,更新 Git 的索引/暂存区。如果您愿意,您可以使用工作树中留下的文件来实现此目的:Git 会尝试在此处留下一些有用的东西供您使用。但与任何提交一样,对 Git 而言真正重要的是其索引中的文件副本。

(旁注:要更直接地查看 Git 索引中的内容,请使用 git ls-files --stage。这会在大型存储库中产生大量输出。git status 命令是查看 Git 索引中内容的更有用的方法,以更紧凑的形式:Git 通过 比较HEAD 提交中的内容,然后还通过将那里的内容与工作树中的内容进行比较来告诉您那里有什么。只有那些 em>不同在这里被提及。这样,如果你有九千个文件,但只有更改其中三个,你只需要查看三个文件名,而不是全部 9000。)

与往常一样,一旦您准备好正确的文件,您必须使用 git add 让 Git 更新其索引。向后添加冲突文件会“折叠”该文件的扩展索引条目,从而解决该文件的合并冲突。或者,如果解决问题的正确方法是删除该文件,您可以使用git rm 来执行此操作。解决所有冲突后,您可以再次运行git merge 以完成合并:

git merge --continue

或者,由于历史原因,您可以运行git commit 来完成合并:

git commit

Git 会注意到您已经解决了冲突,但仍处于合并的中间,并且会以任何一种方式完成合并。 (使用git merge --continue 目前字面意思运行 git commit,但首先要确保有一个准备好完成的合并。因此使用git merge --continue 会更好一些,但会支持旧的方式或很长一段时间,可能永远。)

合并的最终结果

如果您没有发生冲突,Git 会自行进行新的合并提交。由于您确实遇到了冲突,因此您必须解决它,然后自己完成合并。无论哪种情况,Git 现在都准备好以通常的方式进行新的提交——主要是。新提交将 current 提交作为其 两个 父级之一,但它不仅仅是一个父级,具有 其他提交 作为其第二个父项。

新的合并提交M 仍然有一个快照,就像任何提交一样。这包含(像往常一样压缩和去重)每个文件的完整副本,与您在 Git 的索引/暂存区域中排列这些文件的方式完全相同。但是M 的两个父母是JL。写出提交M后,Git 像往常一样将新提交的哈希 ID 存储到分支名称中,所以我们的图片现在看起来像这样:

             K--L
            /    \
...--F--G--H      M   <-- master (HEAD)
            \    /
             I--J   <-- A_feature

合并操作现已完成。 M快照你放什么(因为git merge 因冲突而停止,这让你有机会放你喜欢的任何文件 进入它)。1M第一个父级L,这是 曾经@的提交987654429@启动时提交;现在HEAD 提交当然是提交MM第二个父节点J,这是您在 git merge 命令中命名的提交。


1请注意,如果您要进行无冲突的合并,git merge other 将自行提交,而快照中的文件 in 是Git 基于合并基础在两个分支提示提交之间进行的自动合并的结果。但是,您可以运行git merge --no-commit:这将像往常一样将合并结果放入索引/暂存区域,但即使没有合并冲突也会停止。您现在可以稍后使用git commitgit merge --continue 完成合并,就好像发生了冲突一样——但您也可以像往常一样修改暂存区域中的内容。

这使您有机会创建evil merge。请参阅链接了解这是什么以及为什么不应滥用此功能。


repo B 中发生了什么

我将此作为练习。使用指向各种提交的各种名称绘制图形。添加新提交,记下您制作的各种快照的不同之处。然后考虑git merge 将如何运行两个git diff 命令:找到合并基础提交,并查看自该快照以来在两个分支提示提交中的每一个中发生了什么变化。考虑一下 Git 将如何尝试组合这些更改。有冲突吗?它们是整个文件/树级/高级别的冲突吗?

【讨论】:

感谢您的反馈和出色的回答。我已经更正了我的问题,使其符合我的要求:...我将合并 A_master 中的 A_feature 和 B_master 中的 B_feature。【参考方案2】:

如果我正确理解您的问题,分支 A_feature 和 B_feature 位于不同的存储库中,因此它们不能相互合并。所以这种情况不可能发生。

【讨论】:

感谢您的评论!我解决了我的问题。我要合并 A_master 中的 A_feature 和 B_master 中的 B_feature。

以上是关于将文件从一个存储库移动到另一个存储库并并行更改的主要内容,如果未能解决你的问题,请参考以下文章

如何将分支内容移动到另一个存储库保留历史记录并避免复制原始存储库的完整历史记录?

我可以将单个文件从 CVS 迁移到 SVN 吗?

如何将 SVN 存储库迁移到另一个 SVN 存储库?

将提交从一个分支移动到另一个分支

Subversion - 将文件夹移动到另一个已经存在的存储库

sh 将子目录从一个git存储库移动到另一个git存储库的子目录,而不会丢失提交历史记录。