如何在未检出的分支上执行非快进式的git合并?

11
我现在在分支a上。我想将分支b合并到分支c中,此次合并不是快进型的,但也不需要手动解决冲突。(即它不是最简单的情况,但也不是最困难的情况,所以这是一种Git能够在没有人为干涉的情况下自行完成的合并)。
在不需要检出任何分支的情况下,我有方法可以将b分支合并到c分支吗?如何操作?
更新:如果您知道其他能够完成此任务的Git实现方式,那么这也是一个有效的解决方案。但编写一个可以编程化地进行检出的脚本不是一个好的解决方案,因为它仍然需要我拥有一个干净的工作目录。

git push . 中的点号代表什么意思? - Ram Rachum
另外,请注意我在问题中指定合并可能不是快进合并。这样还有效吗? - Ram Rachum
@grosshat: 谢谢链接。我读了那些答案,但对我来说没有什么有用的内容。(它涉及可以快速前进合并。) - Ram Rachum
有没有一个脚本可以将更改存储,切换分支并执行合并,然后返回分支并应用更改? - saeedgnu
@ilius:请查看对该答案的评论。 - Ram Rachum
显示剩余4条评论
5个回答

8

如果合并不涉及在两个分支上都修改的文件,则需要使用 git read-treegit write-tree,同时使用一个旁路 GIT_INDEX_FILE。以下是具体步骤:

#!/bin/sh
export GIT_INDEX_FILE=.git/aux-merge-index
trap 'rm -f '"'$GIT_INDEX_FILE'" 0 1 2 3 15
set -e
git read-tree -im `git merge-base $2 $1` $2 $1
git write-tree \
| xargs -i@ git commit-tree @ -p $2 -p $1 -m "Merge $1 into $2" \
| xargs git update-ref -m"Merge $1 into $2" refs/heads/$2

你可以使用 git mktree </dev/null 替代 merge-base 来将 bc 视为完全不相关的分支,并使得合并结果将每个分支中的文件组合起来,而不是将不在任一分支中的文件视为删除。
你说你的合并不是快进式的,所以你需要阅读 read-tree 文档,以便让上述序列完全按照你的要求执行。 --aggressive 看起来可能是正确的选择,具体取决于你的分支之间的实际差异。 编辑:添加了空树基础来处理不相关的树。编辑 2:梳理了我在注释中说或留下的一些有效信息。

1
好的,我的脑袋已经被成功地炸了。能否解释一下你在这里做了什么? - Ram Rachum
1
索引基本上只是指向对象数据库的缩略图,并结合工作树中文件的时间戳。read-tree从指定的树中加载索引。选项-i表示忽略工作树,选项-m表示进行简单合并。write-tree将索引状态放入仓库中,commit-tree为此提交创建一个提交,update-ref更新引用。您将不得不阅读有关read-tree合并选项的文档,这里设置的内容不会在任何情况下删除文件,这可能不是您想要的。如果您希望删除任一分支的删除传播,请删除git mktree </dev/null参数。 - jthill
好的,我终于有时间去尝试了。我只需要将 I@ 改为 i@ 就可以了,然后它就能正常工作了!(是笔误吗?)如果这个方法可靠地工作,那肯定比使用辅助仓库的方法要好得多。现在有几个问题:1. 我希望它使用完全标准的合并策略。它是这样做的吗?2. 我们能否让它为合并创建一个标准的提交信息? - Ram Rachum
我现在尝试在实际场景中运行此脚本(当然使用正确的分支名称),尽管合并是平凡快进的,但它失败了。 它出现了 my_project/templates/registration/my_template.html: unmerged (a0bc9afd6f099abb370bb5d41325bb77d3729fb3) 的错误信息,然后又出现了一堆错误信息。 你有任何想法为什么会这样吗? - Ram Rachum
@RamRachum 好的,我认为现在答案会更直接有帮助。您仍然需要进行一些研究并根据自己的情况进行调整,因为低级工具可以使您的生活变得非常轻松,因为它们是直接而强大的。此外,关于退出代码,是的,您可以编写一个陷阱并将其放入脚本中,但是由于结尾处的unset意味着这种事情您只需键入即可。人们不知道实际上损坏git存储库有多难。您能做的最糟糕的事情就是留下实验性提交,并且启用实验性提交对我来说是dvcs的全部意义。 - jthill
显示剩余7条评论

5
考虑到您不想清理工作目录,我假设您的意思是您不想通过某些脚本清理您的工作树或索引。在这种情况下,您无法在当前本地存储库的范围内找到解决方案。Git 在合并时广泛使用索引。如果没有冲突,我不确定工作树是否也是如此,但总的来说,合并与当前检出的分支密不可分。
不过,还有另一种方法,不需要您更改当前存储库中的任何内容。但是,它要求您拥有或创建存储库的克隆版本。基本上,只需克隆您的存储库,然后在克隆版本中执行合并操作,并将其推回到原始存储库。以下是一个简短的示例,说明它将如何工作。
首先,我们需要一个样例存储库。以下命令序列将创建一个存储库。您最终会以 master 作为当前分支,并且有两个名为 change-foochange-bar 的其他分支进行合并的更改。
mkdir background-merge-example
cd background-merge-example
git init
echo 'from master' > foo
echo 'from master' > bar
git add .
git commit -m "add foo and bar in master" 
git checkout -b change-foo
echo 'from foo branch' >> foo
git commit -am "update foo in foo branch"
git checkout -b change-bar master
echo 'from bar branch' >> bar
git commit -am "update bar in bar branch"
git checkout master

现在,想象一下你正在工作的是master分支,你想要将change-bar合并到change-foo中。这里是一个半图形化的描述:

$ git log --oneline --graph --all
* c60fd41 update bar in bar branch
| * e007aff update foo in foo branch
|/  
* 77484e1 add foo and bar in master

以下步骤将完成合并操作,不会影响当前主分支。将其封装成脚本,便可得到一个漂亮的“后台合并”命令:
# clone with absolute instead of relative path, or the remote in the clone will
# be wrong
git clone file://`realpath .` tmp
cd tmp
# this checkout auto-creates a remote-tracking branch in newer versions of git
# older versions will have to do it manually
git checkout change-foo
# creating a tracking branch for the other remote branch is optional
# it just makes the commit message look nicer
git branch --track change-bar origin/change-bar
git merge change-bar
git push origin change-foo
cd ..
rm -rf tmp

简要来说,这将把当前存储库克隆到一个子目录中,进入该目录,执行合并,然后将其推回原始存储库,完成后删除子目录。在大型项目中,您可能希望有一个专用的克隆版本仅保持最新状态,而不是每次都进行全新克隆。合并和推送之后,我们会得到以下结果:
$ git log --oneline --graph --all
*   24f1916 Merge branch 'change-bar' into change-foo
|\  
| * d7375ac update bar in bar branch
* | fed4757 update foo in foo branch
|/  
* 6880cd8 add foo and bar in master

有问题吗?

感谢您的详细说明,我还在消化中。与此同时,有几个问题:1. 我记得读到过有三种 git clone 类型:标准、完全复制和共享。(术语可能是 git-gui 特有的。)我只用过完全复制。我应该使用其他类型吗?2. 我听说 Git 也允许“裸”仓库,即没有工作目录。这种仓库是否允许合并操作?如果是,那么这是否是临时仓库的好选择? - Ram Rachum
2
三种类型:浅层、裸露和“正常”。在这种情况下不建议使用 git 仓库的浅层复制(使用 --depth 选项进行克隆)。我不能保证您能够执行两个分支的合并,特别是如果您错过了这两个分支的共同父节点。但是,是的,git 允许在浅层克隆(非裸露仓库)上进行合并,因为您可以使用 git pull。在这种情况下,我会进行完整/简单的克隆。 - Vincent B.
关于你上面的陈述,“Git在合并时广泛使用索引”:你认为是否有一种方法可以在同一仓库中创建一个辅助索引文件,让Git在其中进行合并,而不是创建另一个仓库? - Ram Rachum
我想说不行,但这只是我的猜测。我想可能可以通过一些符号链接的创造性使用来实现,但这也可能会彻底破坏你的代码库。你所询问的让我觉得你需要查看Git源代码才能得到真正的答案,而这是我从未涉足的领域。很抱歉我不能提供更多帮助。根据我的经验和其他回答的缺乏,我的最终建议是改变你的做事方式,以便你不需要去尝试如何做这件事。听起来很麻烦。 - Ryan Stewart

3

除了@jthill提供的非常复杂的git read-tree答案外,现在有一种我认为更容易的方法,利用git worktree来实现。

这是基本的方法:

$ git worktree add /tmp/wt c
$ git -C /tmp/wt merge b
$ git worktree remove /tmp/wt

以下是一个小的Bash脚本,您可以像这样调用它:
$ worktree-merge c b

Script worktree-merge:

#!/usr/bin/env bash

log() {
    echo -n "LOG: "
    echo "$@" >&2
}

escape() {
    local string="$1"
    echo "${string//[. \/]/-}"
}

cleanup() {
    trap "" SIGINT
    log "Removing temporary worktree '$1' ..."
    git worktree remove --force "$1"
}

prepare_worktree() {
    local reference="$1"
    local worktree="/tmp/MERGE-INTO-`escape "$reference"`"

    log "Creating temporary worktree '$worktree' ..."
    trap "cleanup $worktree" EXIT
    git worktree add --force "$worktree" "$reference"
}

do_merge() {
    local reference="$1"
    local worktree="/tmp/MERGE-INTO-`escape "$reference"`"
    shift

    log "Merging ${@@Q} into ${reference@Q} ..."
    git -C "$worktree" merge "$@"
}

prepare_worktree "$1" &&
do_merge "$@" &&
true

1

即使您的工作目录不干净,您也可以编写脚本。但是您需要先隐藏您所做的更改。

git stash
git checkout c
git merge b
git checkout a
git stash pop

1
我建议使用git stash,这是我经常使用的方式,几乎可以做到与原帖中相同的事情。 - neevek
在Windows上,暂存操作速度较慢(我正在使用的系统),因此不得不等待如此长时间对我来说并不理想。 - Ram Rachum
那么我认为没有其他选择了,无论如何,你想要合并的分支必须是当前分支。如果不进行存储或提交,你就不能离开你当前的分支a...或者有一些Git专家有更好的解决方案吗? - neevek
很遗憾,@RamRachum,我认为你不会有完美的解决方案。你可以尝试使用Mercurial,我听说它在Windows上表现良好。 - Jacob Groundwater
今天我听说它要发布了,就试用了一下,但我认为它并不能解决我的问题。无论我的问题是什么,它都没有键盘快捷键,这对我来说是个致命缺陷。 - Ram Rachum

-1

这个答案解释了一个你可以尝试的解决方法。

不,没有。为了让你能够解决冲突等问题(如果Git无法自动合并它们),需要检出目标分支。

但是,如果合并是快进的,你不需要检出目标分支,因为你实际上不需要合并任何东西 - 你只需要更新分支指向新的头引用。你可以使用git branch -f命令来完成:

git branch -f branch-b branch-a
将更新branch-b指向branch-a的头。


(a) 他说这不是快进。 (b) 这将留下破碎的历史记录。 - jthill
你是对的。我把问题看错了,它确实是快进。我的错。 - sparrow
此外,没有安全检查。如果一开始没有快进会发生什么? - doak

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接