Question

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

Does someone know of an easy way to list remote branches this way?

Was it helpful?

Solution

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 -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'

OTHER TIPS

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 l8tr'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.

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

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...

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 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]

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

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 & 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, totally hacky, but it works! (Mostly. For some reason my last column is getting outdented on the current branch line. But I really should get back to doing actual work instead of tweaking this more.)

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
}

In Powershell, shows branches on the remote that are already merged and at least two weeks old. (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}

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++;
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top