Listing each branch and its last revision's date in Git
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 b开发者_如何学Goy their last modified date, and I can't.
Is there 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 --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
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 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, or add %(color:<color>)
statements in the format to display some fields with a specific color.
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...
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
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]
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
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.)
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}
Taking inspiration from VonC's answer and making improvements (e.g. including remote branches, avoiding Perl, replacing git show
by git log
):
for k in $(git branch -a | sed -e 's/^..//' | grep -v -- '->'); \
do echo -e $(git log -1 --pretty=format:"%ci %cr" $k) \
\\t$k; done | sort -r
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++;
}
?>
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 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
}
精彩评论