There's no way to 100% reliably meet all of your requirements. LeGEC's answer addresses the specific example in your original question, but I don't guess your follow-up to that comes as much surprise.
For your basic requirement, you can try something like
git log --graph --oneilne --first-parent main feature
and if you're following typical conventions then this will likely give you what you want. But there are circumstances in which it won't. I'll come back to that.
For your "bonus", you can do something like
git log --graph --oneline --first-parent $(git merge-base main feature) feature
So instead of main
you say that you want the history up to the last commit that is reachable from both branches.
I don't know a way to dynamically mark the merge base commit in the log (the additional bonus). You could tag your merge bases I guess.
git tag was_on_master $(git merge-base main feature)
git log --graph --oneline --first-parent feature was_on_master
git tag -d waS_on_master
So now... when won't it work, and why is it this way?
In git, commits do not "belong to" branches. There is no history telling you which branch a commit "was created on". The only relationship between branches and commits is that a commit either is, or is not, reachable from a given branch. In the case of merge commits, you can also use the order of the parent pointers to distinguish which "direction" the merge probably happened in.
If you're on branch_A
and you say git merge branch_B
, then the commit you're on (the history "from" branch_A
) will be the first parent of the merge. That's what --first-parent
will later follow. And, if when you give the merge command you actually have branch_A
checked out (as opposed to having the same commit checked out in detached HEAD state), branch_A
automatically moves onto the merge; so the assumption is that typically "first parent means the history of this branch".
But there are situations where someone might do something that messes that up. Suppose you have
... A <--(branch_A)
... B <--(branch_B)
and then you say
git checkout branch_A
git merge branch_B
git checkout branch_B
git merge branch_A
Unless your configuration prevents it, the 2nd merge will be handled as a fast-forward, resulting in
... A -- M <--(branch_A)(branch_B)
/
... B
Now no matter which branch is checked out, git log --first-parent
will go to A
.