Git Cherry
git cherry finds the commits in a range that have no equivalent upstream, which is the list you actually need to port.
git cherry compares a range of commits against an upstream and prints only the
ones that are not already there. It answers a specific question: of the commits
on my branch, which still need to go somewhere else?
It works by comparing patch ids rather than commit hashes. Two commits that
produce the same diff get the same patch id, whatever their hashes, so a commit
you cherry-picked, rebased or reapplied is recognised as already-present. That is
the part git log --left-right cannot do.
The shape
git cherry <upstream> <head>
Usually the upstream is a remote branch:
git cherry -v origin/main feature-search
A - means no equivalent exists upstream, so the commit is unique to your branch
and still needs porting. A + means an equivalent is already there.
The use: a patch series against a moved base
Rebase, cherry-pick and git format-patch all produce the situation where you
have a series of patches meant for a branch that has moved on:
git cherry -v upstream/v1.4 1.4-backports
Every line with a - is a commit that is not upstream. Re-generate only those,
and your backport contains nothing that was already merged.
The use: finding unmerged work
git cherry -v origin/main HEAD
A quick audit of how far your branch has diverged in substance rather than in history. It is also the check to run before opening a pull request that was based on a fork that has since moved.
Why not just diff
git diff main...topic compares the trees and shows the aggregate result. If a
commit was merged upstream and then reverted, or cherry-picked and then
squashed, the diff is empty while your branch still has commits to integrate.
git cherry compares each commit individually and does not care whether the net
effect is already present.
Caveats
Patch identity is a heuristic based on the diff, so two genuinely different commits that happen to produce identical diffs are treated as the same, and a commit whose message-only change produces an empty diff is always considered already applied. It answers "is this change already upstream", not "did this commit".
To see the comparison in full — which commit matched which, and what changed where it did not — use Git Range Diff.