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
.
中任何文件的共同祖先
我想对从 ChangeSet
到 Repo1
的更改进行 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 master
和 git 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 --orphan
和 git 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
我有一个 git 存储库,将其命名为 Repo1
:
Repo1
Folder1
Other stuff...
我有两个文件夹,其中包含 Repo1
中文件的子集。 Baseline
个文件夹:
Baseline
Folder1
...和一个 ChangeSet
文件夹:
ChangeSet
Folder1
Baseline
包含来自 Repo1
的文件,这些文件代表 ChangeSet
.
我想对从 ChangeSet
到 Repo1
的更改进行 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 master
和 git cherry-pick xxx
。替换为您用来保存这两个提交的任何分支名称。
[下面是原始答案。]
I'd like to do a 3-way merge of the changes from
ChangeSet
intoRepo1
. 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 --orphan
和 git 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