Updated answer
I just had a simple idea which contains both
- the performance advantage of ~20% of my original answer compared to FelipeC's, avoiding to call both
git log (once) and git show (for each commit), and
- the advantage of FelipeC's better pageability (found commits are processed one by one, not after processing the whole output like in my original answer).
This is achieved by no longer using GNU sed's subshell execution mode but by reading the output line by line, replacing the Git hash in lines "Branches: 8ba14..." by the result of git branch --contains 8ba14.... In this simple solution, I do not even use sed or awk anymore, just Bash's built-in substring functionality:
#!/usr/bin/bash
#less -FRX < <(
git --no-pager log --quiet --pretty=format:"commit %H%nAuthor: %an <%ae>%nDate: %ad%nBranches: %H%n%n%w(76,4,4)%B" "$@" | while read -r LINE; do
if [ "${LINE:0:10}" = "Branches: " ]; then
echo "Branches: $(git branch --contains ${LINE:10} --format='%(refname:short)' | tr '\n' ' ')"
else
echo "$LINE"
fi
done
#)
Uncomment less paging according to your own preference. Thanks to FelipeC for discussing and comparing our original solutions with one another, helping me come up with this improved version.
Original answer
Out of the box, Git cannot embed a shell script in its pretty-format string. Here is a quick & dirty solution. It kind of mimics the format you want, but without colour and without some of the extra information Git might print in certain situations. But I hope it gives you a clue how to continue and refine it.
Preconditions:
- You use a UNIX-like shell, I tried Git Bash on Windows.
- You use GNU sed.
Define this shell function in your profile or directly on the console:
git_log_branches() {
git log --pretty=format:"commit %H%nAuthor: %an <%ae>%nDate: %ad%nBranches: %H%n%n%w(76,4,4)%B" "$@" |
sed -E "s/^(Branches: )(.*)/echo -n '\1'; git branch --contains \2 --format='%(refname:short)' | tr '\n' ' '/e"
}
The log output would look something like this:
$ git_log_branches -2 HEAD~100
commit d81a845b61f5b98b217722122c6005cb51f9e160
Author: Alexander Kriegisch <aaaa@bbb.xy>
Date: Sun Jun 6 13:27:03 2021 +0700
Branches: main openj9-jit openj9-jit-exclude
Integration test POM (group ID) + UML whitespace cosmetics
commit 25eafcc93340ee2ee6ce05d0ec1a2139e20d45d8
Author: Florian Lasinger <ggg@hhh.yz>
Date: Fri Feb 19 13:57:05 2021 +0100
Branches: main openj9-jit openj9-jit-exclude
[#92] Dependency artifacts have higher precedence than reactor artifacts
(cherry picked from commit f32367b3 + additional comment)
The output is not auto-paged. I do not like this solution much, but it is the best I could come up with, playing for a little while.
Update: Like FelipeC mentioned in his answer, of course you can also transform the shell function into a stand-alone shell script, name it git-log-branches and put it anywhere in your PATH, so Git can find it:
#!/usr/bin/bash
git log --pretty=format:"commit %H%nAuthor: %an <%ae>%nDate: %ad%nBranches: %H%n%n%w(76,4,4)%B" "$@" |
sed -E "s/^(Branches: )(.*)/echo -n '\1'; git branch --contains \2 --format='%(refname:short)' | tr '\n' ' '/e"
Then you call it with git log-branches -2 HEAD~100 in order to get the exact same log output as above when calling the corresponding shell function.
I also compared log calls with regard to timing:
# OK, we have a history of 577 commits
$ git log --oneline | wc -l
577
# Generating a standard Git log is really quick!
$ time (git log | wc -l)
5471
real 0m0.100s
# As expected, kriegaex's solution is way slower. This is the
# price you pay if you want the branches for each commit.
$ time (git log-branches | wc -l)
6591
real 0m56.953s
# The performance of FelipeC's solution is in the same order of magnitude,
# just slightly slower. No big deal. Both solutions could be tweaked
# here or there.
$ time (git log-branches2 | wc -l)
6592
real 1m8.982s
Update regarding -G foo: When using -G, of course the filtering in Git as such takes most of the time, and the result are way fewer commits to process. Therefore, measuring overall performance - not talking about time to display the first hit here - does not really help. My measures above are more meaningful, because they affect all commits which would be processed in a log with more entries. But FWIW, now the numbers look like this:
$ time (git log -G foo --oneline | wc -l)
19
real 0m0.401s
$ time (git log-branches -G foo | wc -l)
216
real 0m2.683s
$ time (git log-branches2 -G foo | wc -l)
217
real 0m2.691s
Again, both solutions take similar amounts of time, but more than normal git log. No surprises here.
Now let us switch to a bigger OSS project - I chose Eclipse AspectJ - and search for -G foo there. Luckily, out of 8607 commits on the main branch, 707 contain "foo", i.e. not just a handful. So a performance measurement actually says something meaningful about the performance of the two scripts vs. Git itself:
# 8,607 commits in total
$ time (git log --oneline | wc -l)
8607
real 0m0.464s
# 707 commits contain "foo"
$ time (git log -G foo --oneline | wc -l)
707
real 0m55.196s
# Script by kriegaex
$ time (git log-branches -G foo | wc -l)
5450
real 1m15.749s
# Script by FelipeC
$ time (git log-branches2 -G foo | wc -l)
5451
real 1m19.283s
Here, we can see that of 75 (kriegaex) or 79 (FelipeC) seconds necessary to process all 707 commits, which really is quasi the same IMO, 55 seconds alone are used by Git to actually filter the big repository with 8,607 commits in the branch in question. I.e. 55/75 or 73,3% of the time is used by Git, and we are no longer comparing the actual script performance alone. The ratio would asymptotically creep up to 100%, the fewer commits are found by -G. This is why initially, I measured without the costly -G option, because it only pollutes the result.