I've updated the answer to the question you linked-to, to note that git mff
(git merge --ff-only
) doesn't require a hash ID. But your particular goal—to move to a given commit that does not have a specific name on it—does require a hash ID or a relative expression. As ElpieKay notes, any relative expression must take the commit graph's "shape" into account.
I like to draw my commit graphs horizontally rather than vertically. Git draws them vertically, with newer commits towards the top, resulting in the kind of git log --decorate --oneline --graph
output ElpieKay showed. I draw them with newer commits towards the right to emphasize the parallel nature of a branch-and-merge within a single branch:
I--J
/ \
...--G--H M--N <-- branch
\ /
K--L
Suppose you have commit G
checked out, as a detached HEAD
. You might like to move "forward" one commit. That's pretty straightforward: one commit closer to commit N
, at the tip of branch branch
, is commit H
. So if you step forward one commit, that's where you wind up.
But which commit is one step forward from H
? Is it commit I
, or commit K
? Or is it, perhaps, both commits, I
and K
?
(Spoiler: it's both.)
This means your goal, of moving forward 1 or N commits, may be impossible. If there is a branch-and-merge bubble here, you must pick one "side" to travel. You can, of course, go back and try the other side as well, but you need to know that you've hit this case.
If you don't have this problem, you can still have a related problem. Suppose we have this graph:
I--J <-- branch1
/
...--G--H
\
K--L <-- branch2
If you are on a detached HEAD at commit G
, the next commit forward is commit H
, but after that, the next commit forward requires an in the direction of clause: are we trying to move towards branch1
, i.e., commit J
? Or are we trying to move towards branch2
, i.e., commit L
?
In this particular case, git rev-list --ancestry-path
can come to our rescue. We can automate the idea of moving forward one commit in some direction, because:
git rev-list --topo-order --ancestry-path HEAD..branch1
will list out, in Git's natural (backwards) order, the commits "between" the current commit (HEAD
) and branch1
that are:
- descendants of
HEAD
(not includingHEAD
itself), and
- ancestors of
branch1
(including the commit identified by the name branch1
)
This constraint—descendants of HEAD
and ancestors of branch1
—is precisely what --ancestry-path
means.
In our example case means that we'll list out commits H-I-J
, but in the other order. So the last commit hash ID listed out is the one we want.1 We can therefore make an alias that will allow us to move our detached HEAD or current branch name, whichever we're on, one step forward in the direction of some given branch name, with:
target=$(git rev-list --topo-order --ancestry-path $endpoint | tail -n 1)
if [ "$target" = "" ]; then
echo "we have reached the endpoint $endpoint"
else
git merge --ff-only $target
fi
You can wrap this up into a small script that lets you move forward one commit at a time. The work needed to make it advance n steps, n > 1, is left as an exercise.
1It's temping to add --reverse
and -n 1
to this, but alas, that doesn't work. You can use --reverse
and head -n 1
instead of using tail -n 1
, but I dislike the potential broken-pipe error this can cause.
The --topo-order
option is mainly paranoia and probably not required, but I haven't proved that to myself, so I have left it in the answer here as a bit of magic.