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.)