I want to know if a particular merge will resolve via "fast-forward" or not before running the merge command.
I know I can specifically request for the merge to not be resolved via "fast-forward" (using the --no-ff
option). Or that I can try to resolve the merge only by fast-forward (using the --ff
option).
But sometimes I want to know if a particular merge is going to resolve via fast-forward before I run it. I realise that I can in theory work it out by digging through the history tree. And I also realise that I could run the merge and see what happens, but this becomes problematic if I then decide I would prefer for the merge to be resolved in the other way, as I have to undo the merge (by repointing the branch tags in the ref-log) and do it again.
NOTE: The --dry-run
question (Is there a git-merge --dry-run option?) is much more about looking at what merge conflicts may exist in a merge, and not about merges that may resolve via fast-forward.
Summary: git merge-base --is-ancestor
tests whether one commit is an ancestor of another (where commits are considered to be their own ancestors, which is a particularly weird form of incest, perhaps :-) ). Since a branch label can only be fast-forwarded by git merge
when the current branch (HEAD
) points to a commit that is an ancestor of the other commit, we can use this to determine whether git merge
could do a fast-forward operation.
It looks like you wanted this posted as an answer, so I have converted it to a working git alias, which you can put in your global git configuration. The alias is a bit long and complicated and it would probably be best to just cut-and-paste this into your git config alias section:
canff = "!f() { if [ $# -gt 0 ]; then b=\"$1\"; git rev-parse -q --verify \"$b^{commit}\" >/dev/null || { printf \"%s: not a valid commit specifier\n\" \"$b\"; return 1; } else b=$(git rev-parse --symbolic-full-name --abbrev-ref @{u}) || return $?; fi; if git merge-base --is-ancestor HEAD \"$b\"; then echo \"merge with $b can fast-forward\"; else echo \"merge with $b cannot fast-forward\"; fi; }; f"
Here is the same thing written as a shell script, in a more readable fashion, and some commentary:
#! /bin/sh
#
# canff - test whether it is possible to fast-forward to
# a given commit (which may be a branch name). If given
# no arguments, find the upstream of the current (HEAD) branch.
# First, define a small function to print the upstream name
# of the current branch. If no upstream is set, this prints a
# message to stderr and returns with failure (nonzero).
upstream_name() {
git rev-parse --symbolic-full-name --abbrev-ref @{u}
}
# Now define a function to detect fast-forward-ability.
canff() {
local b # branch name or commit ID
if [ $# -gt 0 ]; then # at least 1 argument given
b="$1"
# make sure it is or can be converted to a commit ID.
git rev-parse -q --verify "$b^{commit}" >/dev/null || {
printf "%s: not a valid commit specifier\n" "$b"
return 1
}
else
# no arguments: find upstream, or bail out
b=$(upstream_name) || return $?
fi
# now test whether git merge --ff-only could succeed on $b
if git merge-base --is-ancestor HEAD "$b"; then
echo "merge with $b can fast-forward"
else
echo "merge with $b cannot fast-forward"
fi
}
The shell script just needs a main section to drive it, which is the call to f
after the alias. The alias itself simply shovels everything from canff
and upstream_name
into one line. Git's config file rules then require that the entire alias be quoted with double quotes, which in turn requires that all internal double quotes be converted to backslash-double-quote sequences.
(I also took out the local b
statement, since as an alias, this fires up a new instance of the shell each time, so variable-name hygiene becomes unimportant.)
(It's actually possible to write the alias as multiple lines. Just prefix each newline with backslash. However, this alias is so complex that it looks ugly that way too, so I ended up just leaving it one big line.)
You could test if git merge-base <branch1> <branch2>
is equal to git rev-parse <branch1>
. If equal, a ff merge or already-up-to-date when you run git merge <branch1> <branch2>
. If not, a non-ff merge.
function isff(){
a=$(git merge-base $1 $2)
b=$(git rev-parse $1)
c=$(git rev-parse $2)
if [[ "$b" == "$c" ]] || [[ "$a" == "$c" ]];then
echo merge dry run: already up-to-date
return
fi
if [ "$a" == "$b" ];then
echo merge dry run: a fast forward merge
else
echo merge dry run: a non fast forward merge
fi
}
isff master topic
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With