mirror of
https://github.com/git/git
synced 2024-08-24 18:26:02 +00:00
![Junio C Hamano](/assets/img/avatar_default.png)
Suppose you have this topology, and you are trying to make an octopus across A, B and C (you are at C and merging A and B into your branch). The protoccol between "git merge" and merge strategies is for the former to pass common ancestor(s), '--' and then commits being merged. git-merge-octopus does not produce the final merge in one-go. It iteratively produces pairwise merges. So the first step might be to come up with a merge between B and C: o---o---o---o---C / : / o---o---o---B..(M) / / ---1---2---o---o---o---A and for that, "1" is used as the merge base, not because it is the base across A, B and C but because it is the base between B and C. For this merge, A does not matter. I drew M in parentheses and lines between B and C to it in dotted line because we actually do _not_ create a real commit --- the only thing we need is a tree object, in order to proceed to the next step. Then the final merge result is obtained by merging tree of (M) and A using their common ancestor. For that, we _could_ still use "1" as the merge base. But if you imagine a case where you started from A and M, you would _not_ pick "1" as the merge base; you would rather use "2" which is a better base for this merge. That is why git-merge-octopus ignores the merge base given by "merge" but computes its own. The comment at the end of git-merge-octopus talks about "merge reference commit", that we used to update it to common found in this round, and that that updating was pointless. After the first round of merge to produce the tree for M (but without actually creating the commit object M itself), in order to figure out the merge base used to merge that with A in the second round, we used to use A and "1" (which was merge base between B and C). That was pointless --- "merge-base A 1" is guaranteed to give a base that is no better than either "merge-base A B" or "merge-base A C". So the current code keeps using the original head (iow, MRC=C, because in this case we are starting from C and merging B and then A into it). This trickerly was necessary only because we avoided creating the extra merge commit object M. Side note. An alternative implementation could have been to actually record it as a real merge commit M, and then let the two-commit merge-base compute the base between A and M when merging A to the result of the previous round, but we avoided creating M, at the expense of potentially using suboptimal base in the later rounds. But we do not have to be that pessimistic. We can instead accumulate the commits we have merged so far in MRC, and have merge_bases_many() compute the merge base for the new head being merged and the heads we have processed so far, which can give a better base than what we currently do. Signed-off-by: Junio C Hamano <gitster@pobox.com>
108 lines
2 KiB
Bash
Executable file
108 lines
2 KiB
Bash
Executable file
#!/bin/sh
|
|
#
|
|
# Copyright (c) 2005 Junio C Hamano
|
|
#
|
|
# Resolve two or more trees.
|
|
#
|
|
|
|
LF='
|
|
'
|
|
|
|
die () {
|
|
echo >&2 "$*"
|
|
exit 1
|
|
}
|
|
|
|
# The first parameters up to -- are merge bases; the rest are heads.
|
|
bases= head= remotes= sep_seen=
|
|
for arg
|
|
do
|
|
case ",$sep_seen,$head,$arg," in
|
|
*,--,)
|
|
sep_seen=yes
|
|
;;
|
|
,yes,,*)
|
|
head=$arg
|
|
;;
|
|
,yes,*)
|
|
remotes="$remotes$arg "
|
|
;;
|
|
*)
|
|
bases="$bases$arg "
|
|
;;
|
|
esac
|
|
done
|
|
|
|
# Reject if this is not an Octopus -- resolve should be used instead.
|
|
case "$remotes" in
|
|
?*' '?*)
|
|
;;
|
|
*)
|
|
exit 2 ;;
|
|
esac
|
|
|
|
# MRC is the current "merge reference commit"
|
|
# MRT is the current "merge result tree"
|
|
|
|
MRC=$head MSG= PARENT="-p $head"
|
|
MRT=$(git write-tree)
|
|
CNT=1 ;# counting our head
|
|
NON_FF_MERGE=0
|
|
OCTOPUS_FAILURE=0
|
|
for SHA1 in $remotes
|
|
do
|
|
case "$OCTOPUS_FAILURE" in
|
|
1)
|
|
# We allow only last one to have a hand-resolvable
|
|
# conflicts. Last round failed and we still had
|
|
# a head to merge.
|
|
echo "Automated merge did not work."
|
|
echo "Should not be doing an Octopus."
|
|
exit 2
|
|
esac
|
|
|
|
common=$(git merge-base --all $SHA1 $MRC) ||
|
|
die "Unable to find common commit with $SHA1"
|
|
|
|
case "$LF$common$LF" in
|
|
*"$LF$SHA1$LF"*)
|
|
echo "Already up-to-date with $SHA1"
|
|
continue
|
|
;;
|
|
esac
|
|
|
|
CNT=`expr $CNT + 1`
|
|
PARENT="$PARENT -p $SHA1"
|
|
|
|
if test "$common,$NON_FF_MERGE" = "$MRC,0"
|
|
then
|
|
# The first head being merged was a fast-forward.
|
|
# Advance MRC to the head being merged, and use that
|
|
# tree as the intermediate result of the merge.
|
|
# We still need to count this as part of the parent set.
|
|
|
|
echo "Fast forwarding to: $SHA1"
|
|
git read-tree -u -m $head $SHA1 || exit
|
|
MRC=$SHA1 MRT=$(git write-tree)
|
|
continue
|
|
fi
|
|
|
|
NON_FF_MERGE=1
|
|
|
|
echo "Trying simple merge with $SHA1"
|
|
git read-tree -u -m --aggressive $common $MRT $SHA1 || exit 2
|
|
next=$(git write-tree 2>/dev/null)
|
|
if test $? -ne 0
|
|
then
|
|
echo "Simple merge did not work, trying automatic merge."
|
|
git-merge-index -o git-merge-one-file -a ||
|
|
OCTOPUS_FAILURE=1
|
|
next=$(git write-tree 2>/dev/null)
|
|
fi
|
|
|
|
MRC="$MRC $SHA1"
|
|
MRT=$next
|
|
done
|
|
|
|
exit "$OCTOPUS_FAILURE"
|