Listing each branch and its last revision's date in Git

GitMaintenanceAdministrationBranch

Git Problem Overview


I need to delete old and unmaintained branches from our remote repository. I'm trying to find a way with which to list the remote branches by their last modified date, and I can't.

Is there an easy way to list remote branches this way?

Git Solutions


Solution 1 - Git

commandlinefu has 2 interesting propositions:

for k in $(git branch | perl -pe s/^..//); do echo -e $(git show --pretty=format:"%Cgreen%ci %Cblue%cr%Creset" $k -- | head -n 1)\\t$k; done | sort -r

or:

for k in $(git branch | sed s/^..//); do echo -e $(git log --color=always -1 --pretty=format:"%Cgreen%ci %Cblue%cr%Creset" $k --)\\t"$k";done | sort

That is for local branches, in a Unix syntax. Using git branch -r, you can similarly show remote branches:

for k in $(git branch -r | perl -pe 's/^..(.*?)( ->.*)?$/\1/'); do echo -e $(git show --pretty=format:"%Cgreen%ci %Cblue%cr%Creset" $k -- | head -n 1)\\t$k; done | sort -r

Michael Forrest mentions in the comments that zsh requires escapes for the sed expression:

for k in git branch | perl -pe s\/\^\.\.\/\/; do echo -e git show --pretty=format:"%Cgreen%ci %Cblue%cr%Creset" $k -- | head -n 1\\t$k; done | sort -r 

kontinuity adds in the comments:

> If you want to add it your zshrc the following escape is needed.

alias gbage='for k in $(git branch -r | perl -pe '\''s/^..(.*?)( ->.*)?$/\1/'\''); do echo -e $(git show --pretty=format:"%Cgreen%ci %Cblue%cr%Creset" $k -- | head -n 1)\\t$k; done | sort -r'

In multiple lines:

alias gbage='for k in $(git branch -r | \
  perl -pe '\''s/^..(.*?)( ->.*)?$/\1/'\''); \
  do echo -e $(git show --pretty=format:"%Cgreen%ci %Cblue%cr%Creset" $k -- | \
     head -n 1)\\t$k; done | sort -r'

Note: n8tr's answer, based on git for-each-ref refs/heads is cleaner. And faster.
See also "Name only option for git branch --list?"

More generally, tripleee reminds us in the comments:

> - Prefer modern $(command substitution) syntax over obsolescent backtick syntax.

(I illustrated that point in 2014 with "What is the difference between $(command) and `command` in shell programming?")

> - Don't read lines with for.
> - Probably switch to git for-each-ref refs/remote to get remote branch names in machine-readable format

Solution 2 - Git

Here is what I use:

git for-each-ref --sort='-committerdate:iso8601' --format=' %(committerdate:iso8601)%09%(refname)' refs/heads

This is the output:

2014-01-22 11:43:18 +0100       refs/heads/master
2014-01-22 11:43:18 +0100       refs/heads/a
2014-01-17 12:34:01 +0100       refs/heads/b
2014-01-14 15:58:33 +0100       refs/heads/maint
2013-12-11 14:20:06 +0100       refs/heads/d/e
2013-12-09 12:48:04 +0100       refs/heads/f

For remote branches, just use "refs/remotes" instead of "refs/heads":

git for-each-ref --sort='-committerdate:iso8601' --format=' %(committerdate:iso8601)%09%(refname)' refs/remotes

Building on n8tr's answer, if you are also interested in the last author on the the branch, and if you have the "column" tool available, you can use:

git for-each-ref --sort='-committerdate:iso8601' --format='%(committerdate:relative)|%(refname:short)|%(committername)' refs/remotes/ | column -s '|' -t

Which will give you:

21 minutes ago  refs/remotes/a        John Doe
6 hours ago     refs/remotes/b        Jane Doe
6 days ago      refs/remotes/master   John Doe

You may want to call git fetch --prune before to have the latest information.

Solution 3 - Git

Building off of Olivier Croquette, I like using a relative date and shortening the branch name like this:

git for-each-ref --sort='-authordate:iso8601' --format=' %(authordate:relative)%09%(refname:short)' refs/heads

Which gives you output:

21 minutes ago  nathan/a_recent_branch
6 hours ago        master
27 hours ago    nathan/some_other_branch
29 hours ago    branch_c
6 days ago      branch_d

I recommend making a Bash file for adding all your favorite aliases and then sharing the script out to your team. Here's an example to add just this one:

#!/bin/sh

git config --global alias.branches "!echo ' ------------------------------------------------------------' && git for-each-ref --sort='-authordate:iso8601' --format=' %(authordate:relative)%09%(refname:short)' refs/heads && echo ' ------------------------------------------------------------'"

Then you can just do this to get a nicely formatted and sorted local branch list:

git branches

Solution 4 - Git

Just to add to the comment by @VonC, take your preferred solution and add it to your ~/.gitconfig alias list for convenience:

[alias]  
    branchdate = !git for-each-ref --sort='-authordate' --format='%(refname)%09%(authordate)' refs/heads | sed -e 's-refs/heads/--'

Then a simple "git branchdate" prints the list for you...

Solution 5 - Git

Here is what I came up with after also reviewing this.

for REF in $(git for-each-ref --sort=-committerdate --format="%(objectname)" \
    refs/remotes refs/heads)
do
    if [ "$PREV_REF" != "$REF" ]; then
        PREV_REF=$REF
        git log -n1 $REF --date=short \
            --pretty=format:"%C(auto)%ad %h%d %s %C(yellow)[%an]%C(reset)"
    fi
done

The PREV_REF check is to remove duplicates if more than one branch points to the same commit. (As in a local branch that exist in the remote as well.)

NOTE that per the OP request, git branch --merged and git branch --no-merged are useful in identifying which branches can be easily deleted.

[https://git-scm.com/docs/git-branch]

Solution 6 - Git

Sorted remote branches and the last commit date for each branch.

for branch in `git branch -r | grep -v HEAD`;do echo -e `git show --format="%ci %cr" $branch | head -n 1` \\t$branch; done | sort -r

Solution 7 - Git

I made two variants, based on VonC's answer.

My first variant:

for k in `git branch -a | sed -e s/^..// -e 's/(detached from .*)/HEAD/'`; do echo -e `git log -1 --pretty=format:"%Cgreen%ci |%Cblue%cr |%Creset$k |%s" $k --`;done | sort | column -t -s "|"

This handles local and remote branches (-a), handles detached-head state (the longer sed command, though the solution is kind of crude -- it just replaces the detached branch info with the keyword HEAD), adds in the commit subject (%s), and puts things into columns via literal pipe characters in the format string and passing the end result to column -t -s "|". (You could use whatever as the separator, as long as it's something you don't expect in the rest of the output.)

My second variant is quite hacky, but I really wanted something that still has an indicator of "this is the branch you're currently on" like the branch command does.

CURRENT_BRANCH=0
for k in `git branch -a | sed -e 's/\*/CURRENT_BRANCH_MARKER/' -e 's/(detached from .*)/HEAD/'`
do
    if [ "$k" == 'CURRENT_BRANCH_MARKER' ]; then
        # Set flag, skip output
        CURRENT_BRANCH=1
    elif [ $CURRENT_BRANCH == 0 ]; then
        echo -e `git log -1 --pretty=format:"%Cgreen%ci |%Cblue%cr |%Creset$k |%s" $k --`
    else
        echo -e `git log -1 --pretty=format:"%Cgreen%ci |%Cblue%cr |%Creset* %Cgreen$k%Creset |%s" $k --`
        CURRENT_BRANCH=0
    fi
done | sort | column -t -s "|"

This turns the * that marks the current branch into a keyword, and when the loop body sees the keyword it instead sets a flag and outputs nothing. The flag is used to indicate that an alternate formatting should be used for the next line. Like I said, it is totally hacky, but it works! (Mostly. For some reason, my last column is getting outdented on the current branch line.)

Solution 8 - Git

In PowerShell, the following shows branches on the remote that are already merged and at least two weeks old (the author:relative format starts displaying weeks instead of days at two weeks):

$safeBranchRegex = "origin/(HEAD|master|develop)$";
$remoteMergedBranches = git branch --remote --merged | %{$_.trim()};
git for-each-ref --sort='authordate:iso8601' --format=' %(authordate:relative)%09%(refname:short)' refs/remotes | ?{$_ -match "(weeks|months|years) ago" -and $_ -notmatch "origin/(HEAD|master|qa/)"} | %{$_.substring($_.indexof("origin/"))} | ?{$_ -in $remoteMergedBranches}

Solution 9 - Git

I did a simple alias, not sure if this is what exactly asked, but it is simple

I did this as i wanted to list all the branches not just my local branches, which above commands do only

alias git_brs="git fetch && git branch -av --format='\''%(authordate)%09%(authordate:relative)%09%(refname)'\'"

You can pipe above to grep origin to get only origins

This lists all the branches along with the last date modified, helps me decide which one i should pull for latest version

This results in below type of display

Wed Feb 4 23:21:56 2019 +0230   8 days ago      refs/heads/foo
Tue Feb 3 12:18:04 2019 +0230   10 days ago     refs/heads/master
Mon Feb 9 12:19:33 2019 +0230   4 days ago      refs/heads/bar
Wed Feb 11 16:34:00 2019 +0230  2 days ago      refs/heads/xyz
Tue Feb 3 12:18:04 2019 +0230   10 days ago     refs/remotes/origin/HEAD
Mon Feb 9 12:19:33 2019 +0230   4 days ago      refs/remotes/origin/foo
Tue Feb 3 12:18:04 2019 +0230   10 days ago     refs/remotes/origin/master
Tue Feb 3 12:18:04 2019 +0230   10 days ago     refs/remotes/origin/bar
Tue Feb 3 12:18:04 2019 +0230   10 days ago     refs/remotes/origin/xyz

Try and let me know if it helped, happy gitting

Solution 10 - Git

Or you can use my PHP script, https://gist.github.com/2780984

#!/usr/bin/env php
<?php
    $local = exec("git branch | xargs $1");
    $lines = explode(" ", $local);
    $limit = strtotime("-2 week");
    $exclude = array("*", "master");
    foreach ($exclude as $i) {
        $k = array_search($i, $lines);
        unset($lines[$k]);
    }
    $k = 0;
    foreach ($lines as $line) {
        $output[$k]['name'] = $line;
        $output[$k]['time'] = exec('git log '.$line.' --pretty=format:"%at" -1');
        if ($limit>$output[$k]['time']) {
            echo "This branch should be deleted $line\n";
            exec("git branch -d $line");
        }
        $k++;
    }
?>

Solution 11 - Git

Here's a function you can add to your bash_profile to make this easier.

Usage when in a Git repository:

  • branch prints all local branches
  • branch -r prints all remote branches

Function:

branch() {
   local pattern="s/^..//"
   local arg=""
   if [[ $@ == "-r" ]]; then
      pattern="s/^..(.*?)( ->.*)?$/\1/"
      arg=" -r "
      echo '-r provided'
   fi
   for k in $(git branch $arg | perl -pe "$pattern"); do
      echo -e $(git show --pretty=format:"%Cgreen%ci %Cblue%cr%Creset" $k -- | head -n 1)\\t$k
   done | sort -r
}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionRoni YanivView Question on Stackoverflow
Solution 1 - GitVonCView Answer on Stackoverflow
Solution 2 - GitocroquetteView Answer on Stackoverflow
Solution 3 - Gitn8trView Answer on Stackoverflow
Solution 4 - GityngveView Answer on Stackoverflow
Solution 5 - Gitgo2nullView Answer on Stackoverflow
Solution 6 - GitshwetaView Answer on Stackoverflow
Solution 7 - GitbenkcView Answer on Stackoverflow
Solution 8 - GitDave NeeleyView Answer on Stackoverflow
Solution 9 - GitBasavView Answer on Stackoverflow
Solution 10 - GitLadislav PrskavecView Answer on Stackoverflow
Solution 11 - GitenderlandView Answer on Stackoverflow