I\'m on branch a
. I want to merge branch b
into branch c
. The merge is not a fast-forward, but it also doesn\'t require manual resolut
If the merge doesn't involve files touched on both branches then I think you want git read-tree
and git write-tree
with a sideband GIT_INDEX_FILE. This should do it:
#!/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
You could also use git mktree </dev/null
instead of the merge-base
to treat b
and c
as entirely unrelated branches and make the resulting merge combine the files in each rather than treating files missing in either as deletions.
You say your merge isn't a fast-forward, so you're going to need to read the read-tree
docs to make the sequence above do exactly what you want. --aggressive
looks like it might be right, it depends on what the actual differences between your branches are.
edit added the empty-tree base to handle unrelated trees edit 2 hoisting some payload I said or left implicit in a comment
Additional to the very sophisticated git read-tree
answer by @jthill, there is (nowadays) an IMHO easier way doing this utilising git worktree
.
This is the basic approach:
$ git worktree add /tmp/wt c
$ git -C /tmp/wt merge b
$ git worktree remove /tmp/wt
Below is a little Bash script which you can call like this:
$ worktree-merge c b
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
You can write a script even if your working directory is dirty. You will have to stash your changes first.
git stash
git checkout c
git merge b
git checkout a
git stash pop
Given your requirement to not have to clean your working directory, I assume you mean that you don't want to have to clean either your working tree or index, even via some scripting. In that case, you won't find a solution within the bounds of your current local repo. Git uses the index extensively when merging. I'm not sure about the working tree if there are no conflicts, but in general, merging is inextricably tied to the currently-checked-out branch.
There's another way, though, that wouldn't require you to change anything in your current repo. It does, however, require you to have or create a clone of your repo. Basically, just clone your repo, then do your merge in the clone, and push it back to your original repo. Here's a brief example of how it would work.
First, we need a sample repo to work with. The following sequence of commands will create one. You'll end up with master
as your current branch and two other branches with changes ready to be merged named change-foo
and change-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
Now, imagine that you're working on master
, and you're wanting to merge change-bar
into change-foo
. Here's a semi-graphical depiction of where we are:
$ git log --oneline --graph --all
* c60fd41 update bar in bar branch
| * e007aff update foo in foo branch
|/
* 77484e1 add foo and bar in master
The following sequence will accomplish the merge without interfering with the current master branch. Pack this into a script, and you've got a nice "background-merge" command:
# 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
Briefly, that will clone the current repo to a subdirectory, enter that directory, do the merge, then push it back to the original repo. It removes the subdirectory after it's done. In a large project, you might want to have a dedicated clone that's just kept up to date instead of making a fresh clone every time. After the merge and push, we end up at:
$ 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
Questions?
This answer explains a workaround that you can try.
No, there is not. A checkout of the target branch is necessary to allow you to resolve conflicts, among other things (if Git is unable to automatically merge them).
However, if the merge is one that would be fast-forward, you don't need to check out the target branch, because you don't actually need to merge anything - all you have to do is update the branch to point to the new head ref. You can do this with git branch -f:
git branch -f branch-b branch-a
Will update branch-b to point to the head of branch-a.