15

Having done git fetch --all --prune to fetch refs from the remote repositories I now want to see the most recent commit on each of the branches on origin

The command

git for-each-ref --format='%(refname:short)' | grep 'origin/'

lists all sixteen branches on origin, and so I would expect the following command to show the most recent commit from each one

git for-each-ref --format='%(refname:short)' | grep 'origin/' | 
xargs git log --source -n 1 

(N.B. This is one line when I run it; I have split it over two for readability.)

However the command does not work, it only lists one commit: it seems the one commit limit applies to the entire list and not to each branch in turn.

What am I doing wrong?

(Note that I have also tried to adapt the solution here

git for-each-ref --format='%(refname:short)' | grep 'origin/' | 
xargs git show --oneline 

but the --oneline flag is ignored and the result is too verbose.)

Community
  • 1
  • 1
dumbledad
  • 16,305
  • 23
  • 120
  • 273

3 Answers3

12

A simpler solution is to use git ls-remote origin:

git ls-remote --heads origin

For example, in the git repo:

C:\Users\vonc\prog\git\git>git ls-remote --heads origin|
117eea7eaaeb5ecb77d9b7cebdb40b4e85f37374        refs/heads/maint
f5b6079871904ba5b0a8548f91545f126caf898b        refs/heads/master
edb03e5a9b9b63d0864557f99f339b2b5f3a9e4e        refs/heads/next
014438bc5bdf9deef0d3888e58c9f76addc1106c        refs/heads/pu
56f24e80f0af4dd3591c8f143183b59cf9a34620        refs/heads/tmp
33f854ca9eb8251f5b7fa1c670d4abcdd76764ce        refs/heads/todo

That will list the most recent commits... even without fetching first!

That can be formatted and aliases easily:

C:\Users\vonc\prog\git\git>
git config alias.rh "!git ls-remote -h origin | while read b; do PAGER='' git log -n1 --color --pretty=format:'%ct%C(yellow)%d%Creset - %Cred%h%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset%n' --abbrev-commit $( echo $b | cut -d' ' -f1 ) --; done | sort -rn -k1,10 | cut -c11-"

Then a simple git rh would give:

C:\Users\vonc\prog\git\git>git rh
 (origin/master, origin/HEAD) - f5b6079 Second batch for 2.7 (3 months ago) <Junio C Hamano>
 (origin/next, next) - edb03e5 Sync with 2.1-rc2 (1 year, 5 months ago) <Junio C Hamano>
 (origin/tmp, tmp) - 56f24e8 completion: handle '!f() { ... }; f' and "!sh -c '...' -" aliases (1 year, 7 months ago) <Steffen Prohaska>
 (origin/pu) - 014438b Merge branch 'nd/multiple-work-trees' into pu (1 year, 10 months ago) <Junio C Hamano>
 (origin/todo) - 33f854c What's cooking (2013/07 #09) (2 years, 5 months ago) <Junio C Hamano>
 (tag: v1.8.3.4, origin/maint) - 117eea7 Git 1.8.3.4 (2 years, 6 months ago) <Junio C Hamano>

That is:

git rh


This alias assume the remote is named 'origin' though.

To make it accept a parameter, it can be changed to a function:

C:\Users\vonc\prog\git\git>git config alias.rh "!f() { git ls-remote -h $1 | while read b; do PAGER='' git log -n1 --pretty=format:'%h%n' --abbrev-commit $( echo $b | cut -d' ' -f1 ) --; done; }; f"
                                                                        ^^

Then:

C:\Users\vonc\prog\git\git>git rh upstream
833e482
7548842
ef7b32d
f67e31e
e2281f4
Community
  • 1
  • 1
VonC
  • 1,262,500
  • 529
  • 4,410
  • 5,250
  • That's amazingly useful - works without fetching first and allows clever formatting options! Thanks. I won't mark it as the answer yet; while I'll probably swap to this technique I would like to know what was failing with my `xargs` approach – dumbledad Jan 10 '16 at 10:31
  • 1
    @dumbledad I have edited the answer to make it accept a parameter. That way, you can query any remote you want (origin or named otherwise) – VonC Jan 10 '16 at 10:42
  • Though the `git ls-remote` bit works without fetching first doesn't the `git log` bit need a fetch to be up-to-date? – dumbledad Jun 05 '16 at 20:02
  • @dumbledad Yes it does. The OP does start with "Having done `git fetch --all --prune` to fetch refs from the remote repositories" though: the fetch was done first.. – VonC Jun 05 '16 at 20:05
8

Combining Andrew C's comment on the original post, that the pattern matching can be included in the git command (instead of using grep), with andreas-hofmann's point (in his answer) that xargs is conflating arguments, then another possible solution is this

git for-each-ref --format='%(refname:short)' refs/remotes/origin/ | xargs -n1 git log --max-count=1 --source

This swaps the pattern matching to occur in the git command and tells xargs to provide at most one argument at a time.

(N.B. The pattern matching in git's for-each-ref command goes from the start of the match so I have to use 'refs/remotes/origin/' instead of the pattern 'origin/' which I could use with grep. And I could replace --max-count=1 with -1 for brevity.)

Community
  • 1
  • 1
dumbledad
  • 16,305
  • 23
  • 120
  • 273
6

The problem is that git log only prints the log of one ref, that is why your xargs call is not working. If you e.g. call git log -n 1 origin/master origin/not_master (using xargs will result in a call like this) you'll get the log up to the most recent commit and that list is then limited to the first entry. I don't know what exactly will happen if the two branches have diverged, but still, you'll limit the complete log output to only one entry.

Additionally to VonC's detailed answer, you can modify your command to achieve the desired result if you wrap the git log command in a loop instead of using xargs:

for ref in $(git for-each-ref --format='%(refname:short)' | grep 'origin/')
do
    git --no-pager log $ref -n 1 --oneline
done

Use the --no-pager option to send the output directly to the console.

andreas-hofmann
  • 2,378
  • 11
  • 15
  • 1
    Wonderful, thanks. For typing at the bash console I went for `for ref in $(git for-each-ref --format='%(refname:short)' | grep 'origin/'); do git --no-pager log $ref -n 1 --source ; done` – dumbledad Jan 10 '16 at 10:41
  • 1
    Good catch on the xargs. +1 – VonC Jan 10 '16 at 10:41