Git 使用指定的祖先文件夹将文件夹合并到存储库中

Git Merge a Folder into a Repository using a Specified Ancestor Folder

我有一个 git 存储库,将其命名为 Repo1:

Repo1
    Folder1
    Other stuff...

我有两个文件夹,其中包含 Repo1 中文件的子集。 Baseline 个文件夹:

Baseline
    Folder1

...和一个 ChangeSet 文件夹:

ChangeSet
    Folder1

Baseline 包含来自 Repo1 的文件,这些文件代表 ChangeSet.

中任何文件的共同祖先

我想对从 ChangeSetRepo1 的更改进行 3 向合并。我已经研究过创建一个包含两个提交的临时存储库,第一个用于基线,第二个用于变更集,然后与 --allow-unrelated-histories:

合并
git merge <remote> --no-commit --allow-unrelated-histories

...但这似乎将任何更改标记为冲突,并且似乎根本没有使用祖先。

我猜我可以使用 git-merge-file 合并所有三个位置可能存在的任何非二进制文件,然后自己处理所有二进制冲突、添加、删除文件等,但我想知道是否有更直接的解决方案。

提前致谢。

编辑:从下面的答案来看,Changeset 可能是更新文件夹的错误选择。可能更好的词是 Snapshot

2021 年更新:这个问题的完整脚本现在在 GitHub 作为 git-stash2d

编辑: 您在自己的回答中走在了正确的轨道上:对于您的实际情况,cherry-pick 几乎肯定是要走的路。诀窍是将他们的原始树作为 "orphan branch"(独立提交),然后将他们的补丁作为该分支上的第二次提交,然后返回到您自己的分支并使用 git cherry-pick . Cherry-picking 在内部实现为完整的三向合并,合并基础是被 cherry-picked 提交的父级,--theirs 提交是您命名的提交。

说明

在您的原始存储库(或为该存储库添加的工作树,如果您不想弄乱您的主工作树),执行:

git checkout --orphan xxx         # use any name you like here
git read-tree -m -u 4b825dc642cb6eb9a060e54bf8d69288fbee4904

此处的哈希 ID 是 the empty tree 的哈希 ID。使用 --empty 逻辑上应该在这里工作,但没有。或者代替读取树,使用:

git rm -r .

它做的事情完全一样,而且更容易输入,但不知何故看起来更可怕。

你的工作树现在应该是空的,git status 会说:

On branch xxx

No commits yet

nothing to commit (create/copy files and use "git add" to track)

如果您的工作树不为空,它以前包含未跟踪的文件,现在仍然存在。您应该移动或删除它们(或者,同样,您可以在添加的工作树中完成所有这些操作)。

现在按照您在自己的答案中的建议进行操作:

# copy my Baseline folder changes in
git add .
git commit -m "baseline"

(旁注:不要使用 git commit -a;它不会做你想要的)。

我理解 "changeset" 的意思是 "a diff you will apply",而不是 "a new set of files"。变更集是描述新快照的错误词,但如果那是新快照,现在是时候再次清空工作树了:

git rm -r .

使用更容易输入的版本。然后,几乎直接来自您自己的答案:

# copy my ChangeSet folder changes in
git add .
git commit -m "code"

您现在可以 git checkout mastergit cherry-pick xxx。替换为您用来保存这两个提交的任何分支名称。

[下面是原始答案。]


I'd like to do a 3-way merge of the changes from ChangeSet into Repo1. I've looked into creating a temporary repository containing two commits,

你至少差一分。合并有 三个 个输入,而不是两个:

the first for the baseline, and the second for the changeset, and then merging with --allow-unrelated-histories:

使用第一个作为基线,您走在正确的轨道上。

您需要的另外两个是:

  • 一个有基线加变化:这是他们的代码,或者合并的--theirs端,和
  • 使用您的代码:这是合并的 --ours 端。由于 运行 宁 git checkout.
  • ,您将作为 HEAD 提交。

从历史上看,这两个提交都必须从基线下降。这样 Git 可以将 merge base 快照(在本例中为基线)与两个 branch tip 快照中的每一个进行比较:你的代码,以及他们的代码被他们的变更集修改。

因此:

# create initial commit in initial repository:
git init         # create new empty repository
...              # copy baseline into place
git add .
git commit

# add their changeset as a new commit on a branch:
git checkout -b theirs
... apply the changeset, perhaps with "git apply" ...
git add -u       # or git add . again, or similar
git commit

# add your version of the code as a new commit on master:
git checkout master
... copy your code into place ...
git add .        # or similar
git commit

现在您可以 运行 git merge theirs。这三个输入是合并基础提交、您当前的提交——master 的尖端,也称为 HEAD——以及你命名的提交:分支 theirs 的尖端提交。

git merge 命令自行定位合并基础提交。在这种情况下,它是初始提交中的基线文件。 git merge 命令现在产生两个变更集:

  • baseline vs HEAD:这就是改变的地方;
  • 基准 vs theirs:这就是 他们 改变的地方。

请注意,第二次比较会生成您用于创建 theirs 提交及其快照的变更集。这似乎是浪费精力——为什么不直接给 Git 变更集呢?——但这正是 Git 本身的构建方式:Git 确实需要那个快照,所以你必须制作它.

如果您已经有一个存储库,并且想直接在那里进行工作怎么办?

在这种情况下,您有点为难(在 "problematic situation" 的意义上)。 Git 自行查找合并库。您不能只告诉 Git: 进行合并,假装提交 C 是某个任意提交 C 的合并基础1

一个选择是将整个存储库重写为允许这样做的结构。这通常是个坏主意,除非你真的想切换到新的历史记录,同时丢弃所有克隆。

另一个是创建第二个存储库,或者在您的存储库中创建一个独立的子图。这工作正常:使用 git checkout --orphangit read-tree --empty -u 为新的断开连接的分支获得一个干净的平板(当然不要调用主分支 master)。然后,您可以将新的合并提交绑定到主图中的原始历史记录中。这有点棘手。

第三种是使用git replace 插入一个父图,这样你的仓库就好像有一个新的根提交。这也有点棘手。它等同于第二种方法,只是它留下的痕迹更少:无论您是否保留替换提交,它都不会在克隆操作中被复制,因此其他人试图弄清楚您是如何做的,估计会不解。

最后一个选项是您自己描述的选项:

... I could use git-merge-file to merge any non-binary files that may exist in all three locations, and then handling all binary conflicts, added, deleted files etc. myself ...

这个方法也很好用,你可以用脚本自动完成很多工作;只是比 Git 做起来要痛苦一些。


1实际上,您可以这样做,使用git merge-recursive。但是,此命令并不意味着用户 运行。没有文档告诉您 如何 到 运行 它,而且参数很复杂:其中一些是作为环境变量提供的!不要这样。

我还没有编写完整的脚本,但这是我认为可行的。

首先,按照我在这里说的做:

I've looked into creating a temporary repository containing two commits, the first for the baseline, and the second for the changeset.

所以我将创建一个临时存储库,其中包含我对基线的提交,例如:

mkdir Temp
cd Temp
git init
# copy my Baseline folder changes in
git add --all
git commit -m "baseline"
rm -rf <folder>
# copy my ChangeSet folder changes in
git add --all
git commit -m "code"

然后我只需要将它挑选到我的 Repo1 存储库中,例如 this:

使用补丁的原始答案 - 这可能并不理想:

git --git-dir='path\to\Temp\.git' format-patch -1 --stdout HEAD | git apply --3way

使用直接的 cherry-pick 更新答案:

git fetch 'path\to\Temp'
git cherry-pick -n FETCH_HEAD