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?
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?
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'
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
You may want to call "git fetch --prune" before to have the latest information.
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...
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
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 branchesbranch -r
prints all remote branchesFunction:
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++;
}