85

I found the below script that lists the branches by date. How do I filter this to exclude newer branches and feed the results into the Git delete command?

for k in $(git branch | sed /\*/d); do 
  echo "$(git log -1 --pretty=format:"%ct" $k) $k"
done | sort -r | awk '{print $2}'
mklement0
  • 382,024
  • 64
  • 607
  • 775
Kenoyer130
  • 6,874
  • 9
  • 51
  • 73

17 Answers17

88

How about using --since and --before?

For example, this will delete all branches that have not received any commits for a week:

for k in $(git branch | sed /\*/d); do 
  if [ -z "$(git log -1 --since='1 week ago' -s $k)" ]; then
    git branch -D $k
  fi
done

If you want to delete all branches that are more than a week old, use --before:

for k in $(git branch | sed /\*/d); do 
  if [ -z "$(git log -1 --before='1 week ago' -s $k)" ]; then
    git branch -D $k
  fi
done

Be warned though that this will also delete branches that where not merged into master or whatever the checked out branch is.

Paul
  • 6,061
  • 6
  • 39
  • 70
Daniel Baulig
  • 10,739
  • 6
  • 44
  • 43
  • 4
    This solution is more elegant than my own. – Julian Apr 26 '12 at 01:22
  • 44
    The above code is nice, but it my testing this deletes only branches that HAVE received commits in the past week. I had to change the -n to -z to make it work. Also, if you want to be a bit more safe, change the -D to -d so that it will only delete merged branches. – ben May 21 '14 at 19:51
  • This didn't work for me until I removed the space between `$k)"` and `]` on line 3. (Could be OSX-specific? I'm not sure) – Alex Flint Sep 01 '16 at 18:00
  • 2
    The example should be using --before instead of --since. Be careful not to run this blindly! – Clay Nov 03 '16 at 21:23
  • 9
    I don't see how this works. `--before` option just returns *any* commit that's older than the given date. So that condition will always return some commit unless your project was just created. I am using OS X (zsh), and above code returns all branches – THIS USER NEEDS HELP Mar 28 '18 at 01:08
  • this .. good answer .. should be edited. -n and -z may be OS specific. certainly though, I wouldnt want to leave advise which is reported by other users to lead to accidentally deleting all records more recent when the intent was older than. – roberto tomás Sep 18 '18 at 18:08
  • How shall I exclude the unmerged branches from here? – SenG Mar 04 '19 at 00:19
  • use `git branch --merged` to only process merged branches – Herr Derb May 08 '19 at 15:21
  • 1
    Testing this command... it seems `-s` should be `-S` (capital S) otherwise `git` expects `--` in the command line – xbmono Oct 20 '19 at 21:35
  • 3
    careful! The second script "older than" will delete all branches. I used this script succesfully https://gist.github.com/AvnerCohen/b8a40c62f8097d8c7b14 – jens Feb 19 '20 at 22:36
  • 2
    The flag in first script should be `-z`, not `-n` - if you want to list all the branches that _haven't_ received a commit in a week. – Dusan Maksic Sep 28 '20 at 08:27
  • This only deletes branches locally. How to apply changes on remote too? – backslashN Jul 05 '22 at 07:21
  • I think `-s` should be `-S` (the latter doesn't seem to be a documented option for 2.39.1) – Ethereal Feb 07 '23 at 02:17
  • @backslashN `git branch -a` - return the list of local and remote branches. – Roman Motovilov Mar 22 '23 at 09:46
56

The poor man's method:

List the branches by the date of last commit:

git branch --sort=committerdate | xargs echo

this will list the branches while xargs echo pipe makes it inline (thx Jesse).

You will see all your branches with old ones at the beginning:

1_branch 2_branch 3_branch 4_branch

Copy the first n ones, which are outdated and paste at the end of the batch delete command:

git branch -D 1_branch 2_branch

This will delete the selected ones only, so you have more control over the process.

To list the branches by creation date, use the --sort=authordate:iso8601 command as suggested by Amy

Remove remote branches

Use git branch -r --sort=committerdate | xargs echo (says kustomrtr) to review the remote branches, than git push origin -d 1_branch 2_branch to delete the merged ones (thx Jonas).

gazdagergo
  • 6,187
  • 1
  • 31
  • 45
  • 1
    `$ git --version` `git version 2.1.4` `$ git branch --sort=committerdate` `error: unknown option 'sort=committerdate'` – user151841 Oct 20 '18 at 16:11
  • 1
    @user151841 that's because it's supposed to say `--sort=-committerdate` – wiredniko Mar 05 '19 at 15:24
  • 2
    This was best for me. You can skip the part where you make them one line in an editor by doing this `git branch --sort=committerdate | xargs echo`. Also, the comment about putting the "-" before committerdate is wrong, that puts the newest ones at the top. Anyway, you can then just copy the ones up to where you want to delete and, put them after the `git branch -D ...` – Jesse Patel Mar 11 '19 at 13:12
  • Thanks @JessePatel, this is the most complete solution. I edited my answer based on your comment. Also removed the "-". (I hope nobody removed any current branch because of to this error...) – gazdagergo Mar 11 '19 at 13:36
  • 2
    `git branch -r --sort=committerdate | xargs echo` In case anyone wants to review the remote branches. – kustomrtr Nov 08 '19 at 21:31
  • 1
    `git push origin -d 1_branch 2_branch` To delete remote branches. – Jonas Pedersen Nov 18 '20 at 19:19
  • 1
    Superb. Certainly not the "poor man's" method :D Thank you! Saved me an hour at least! – Karthik May 11 '23 at 18:47
13

Safe way to show the delete commands only for local branches merged into master with the last commit over a month ago.

for k in $(git branch --format="%(refname:short)" --merged master); do 
  if (($(git log -1 --since='1 month ago' -s $k|wc -l)==0)); then
    echo git branch -d $k
  fi
done

This does nothing but to output something like:

git branch -d issue_3212
git branch -d fix_ui_search
git branch -d issue_3211

Which I copy and paste directly (remove the echo to delete it directly)

This is very safe.

estani
  • 24,254
  • 2
  • 93
  • 76
8

Delete 5 oldest remote branches

git branch -r --sort=committerdate | head -n 5 | sed 's/  origin\///' | xargs git push origin --delete
Novikov
  • 504
  • 6
  • 12
6

This is what worked for me:

for k in $(git branch -r | sed /\*/d); do 
  if [ -z "$(git log -1 --since='Aug 10, 2016' -s $k)" ]; then
    branch_name_with_no_origin=$(echo $k | sed -e "s/origin\///")
    echo deleting branch: $branch_name_with_no_origin
    git push origin --delete $branch_name_with_no_origin
  fi
done

The crucial part is that the branch name (variable $k) contains the /origin/ part eg origin/feature/my-cool-new-branch However, if you try to git push --delete, it'll fail with an error like:
unable to delete 'origin/feature/my-cool-new-branch': remote ref does not exist.
So we use sed to remove the /origin/ part so that we are left with a branch name like feature/my-cool-new-branch and now git push --delete will work.

Komu
  • 14,174
  • 2
  • 28
  • 22
  • I don't think you need `sed /\*/d` when you use `git branch -r` - all it is doing is removing the `*` character that points to your current branch. Which `git branch -r` doesn't do. – Kyle Pittman Aug 19 '21 at 15:48
4

It's something similar to Daniel Baulig answer, but also takes in consideration ben's comment. Also It filters branches by a given patter, since we're using try-XX convention for branching.

for k in $(git branch -r | awk -F/ '/\/YOUR_PREFIX_HERE/{print $2}' | sed /\*/d); do
   if [ -z "$(git log -1 --since='Jul 31, 2015' -s origin/$k)" ]; then
     echo deleting "$(git log -1 --pretty=format:"%ct" origin/$k) origin/$k";
     git push origin --delete $k;
   fi;
done
3615
  • 3,787
  • 3
  • 20
  • 35
4

git branch --sort=committerdate | head -n10 | xargs git branch -D

SD.
  • 1,432
  • 22
  • 38
3

Actually, I found the accepted answer wasn't reliable enough for me because of the ben's comment. I was looking for cleaning up old release branches so it's possible that the top commit has been cherry picked and has an old commit date... Here's my take on it:

REMOTE_NAME=origin
EXPIRY_DATE=$(date +"%Y-%m-%d" -d "-4 week")

git fetch $REMOTE_NAME --prune
git for-each-ref --format='%(committerdate:short) %(refname:lstrip=3) %(refname:short)' --sort -committerdate refs/remotes/$REMOTE_NAME | while read date branch remote_branch; do
    # protected branch
    if [[ $branch =~ ^master$|^HEAD$ ]]; then
        printf "%9s | %s | %50s | %s\n" "PROTECTED" $date $branch $remote_branch
    elif [[ "$date" < "$EXPIRY_DATE" ]]; then
        printf "%9s | %s | %50s | %s\n" "DELETE" $date $branch $remote_branch
        #git push $REMOTE_NAME --delete $branch
    fi
done

You can easily adapt the delete command based on your needs. Use this carefully.

Output example: enter image description here

mathpaquette
  • 406
  • 4
  • 16
2

The above code did not work for me, but it was close. Instead, I used the following:

for k in $(git branch | sed /\*/d); do 
  if [[ ! $(git log -1 --since='2 weeks ago' -s $k) ]]; then
    git branch -D $k
  fi
done
Kirk Strobeck
  • 17,984
  • 20
  • 75
  • 114
1
for k in $(git branch -r | sed /\*/d); do 
  if [ -n "$(git log -1 --before='80 week ago' -s $k)" ]; then
    git push origin --delete "${k/origin\//}"
  fi
done
0

I'm assuming that you want to delete just the refs, not the commits in the branches. To delete all merged branches except the most recent __X__:

git branch -d `for k in $(git branch | sed /\*/d); do
  echo "$(git log -1 --pretty=format:"%ct" $k) $k"
done | sort -r | awk 'BEGIN{ORS=" "}; {if(NR>__X__) print $2}'`

To delete all branches before timestamp __Y__:

git branch -d `for k in $(git branch | sed /\*/d); do
  echo "$(git log -1 --pretty=format:"%ct" $k) $k"
done | sort -r | awk 'BEGIN{ORS=" "}; {if($1<__Y__) print $2}'`

Replace the -d option by -D if you want to delete branches that haven't been merged as well... but be careful, because that will cause the dangling commits to be garbage-collected at some point.

Julian
  • 4,176
  • 19
  • 40
0

Based on @daniel-baulig's answer and the comments I came up with this:

for k in $(git branch -r --format="%(refname:short)" | sed s#^origin/##); do
   if [ -z "$(git log -1 --since='4 week ago' -s $k)" ]; then
    ## Info about the branches before deleting
    git log -1 --format="%ci %ce - %H $k" -s $k;
    ## Delete from the remote
    git push origin --delete $k;
    ## Delete the local branch, regardless of whether it's been merged or not
    git branch -D $k
  fi;
done

This can be used to delete all old branches (merged or NOT). The motivation for doing so is that it is unlikely that branches that has not been touched in a month rot and never make it to master. Naturally, the timeframe for pruning old branches depends on how fast the master branch moves.

George
  • 309
  • 3
  • 6
0

Sometimes it needs to know if a branch has been merged to the master branch. For that purpose could be used the following script:

#!/usr/bin/env bash

read -p "If you want delete branhes type 'D', otherwise press 'Enter' and branches will be printed out only: " action
[[ $action = "D" ]] && ECHO="" || ECHO="echo"

for b in $(git branch -r --merged origin/master | sed /\*/d | egrep -v "^\*|master|develop"); do
  if [ "$(git log $b --since "10 months ago" | wc -l)" -eq 0 ]; then
    $ECHO git push origin --delete "${b/origin\/}" --no-verify;
  fi
done

Tested on Ubuntu 18.04

wwwebman
  • 1,719
  • 1
  • 14
  • 15
0

For how using PowerShell:

  • Delete all merged branches excluding notMatch pattern
git branch -r --merged | Select-String -NotMatch "(^\*|master)" | %{ $_ -replace ".*/", "" } | %{ git push origin --delete $_ }
  • List all merged branches in txt file
git branch -r --merged | Select-String -NotMatch "(^\*|master)" | %{ $_ -replace ".*/", "" } | Set-Content -Path .\deleted-branches.txt
akramgassem
  • 111
  • 2
  • 9
0

The branches can be found with this one liner:

git for-each-ref --sort=committerdate refs/heads --format='%(refname:short) %(committerdate:unix)' | awk '{if ($2 + 7 * 24 * 3600 < systime() ) print $1}'
Kjeld Flarup
  • 1,471
  • 10
  • 15
0

Delete git branches older than period

This is an automated bash script to delete git branches older than a specific period. The default set time is 3 months, but you can pass the period in months as a first parameter while running the shell script.


#!/bin/sh

:'
  This is an automated bash script to delete git branches older than some specific time period.
  The default set time is 3 months, but you can pass the period in months as a first parameter while running the shell script
'
declare -i numberOfMonths=3 # Declare the default period in months
declare blackListedBranches # Blacklisted branches
clear=clear # Command to clear terminal
ECHO='echo ' # Custom echo

${clear} # Clear terminal

# Check for passed period(In months) parameter
if [ -z "$1" ]; 
then
  # Period not set
  echo ""
  echo "Period not set. Assuming delete period to the default $numberOfMonths months period!"
  echo ""
  sleep 2 # Hold for 2 seconds
else
  # Period set
  numberOfMonths=$1 # Set passed period
fi

echo "Deleting branches older than $numberOfMonths months!"
echo ""
sleep 1 # Hold for a second


# Set branches to exclude in deletion
blackListedBranches="main,master,development"



# Check for trailing commas to remove them
if [[ "$blackListedBranches" == *, ]]; 
then  
  declare trimmedValue=$(sed 's/.\{1\}$//' <<< "$blackListedBranches") # Remove last character
  blackListedBranches="$trimmedValue" # Re-assign value
fi

# Start loop to search for commas
while true; do
  # Check for commas in case of comma separated list to create a list for GREP
  case "$blackListedBranches" in 
    *,*)
      declare branchSeparator="$\|" # Declare separator syntax
      blackListedBranches=${blackListedBranches/,/$branchSeparator} # Replace comma with GREP separator syntax
      ;;
    *) # Default
      break # Break loop
    ;;
  esac
done

blackListedBranches="${blackListedBranches}$" # Append dollar sign at the end of GREP list

echo "The branches [ $blackListedBranches ] will not be deleted!"
sleep 2 # Hold for 2 seconds

echo "The branches below will be deleted!"
sleep 1 # Hold for 2 seconds
git branch -a | sed 's/^\s*//' | sed 's/^remotes\///' | grep -v $blackListedBranches
sleep 4 # Hold for 2 seconds

: '
  Initiate loop scanning for branches older than passed time or set default while excluding the below branches
  main, master
'
for target_branch in $(git branch -a | sed 's/^\s*//' | sed 's/^remotes\///' | grep -v $blackListedBranches); 
do
  # Check period
  if ! ( [[ -f "$target_branch" ]] || [[ -d "$target_branch" ]] ) && [[ "$(git log $target_branch --since "$numberOfMonths month ago" | wc -l)" -eq 0 ]]; then
    if [[ "$DRY_RUN" = "false" ]]; 
    then
      ECHO="" # Empty echo
    fi

    local_target_branch_name=$(echo "$target_branch" | sed 's/remotes\/origin\///') # Get target branch in iteration
    local_target_branch_name=${local_target_branch_name/origin\//} # Replace string "origin/" with empty(string)
    $ECHO Deleting Local Branch : "${local_target_branch_name}" # Print message
    sleep 1 # Hold for a second
    git branch -d "${local_target_branch_name}" # Delete local branch
    $ECHO Deleting Remote Branch : "${local_target_branch_name}" # Print message
    sleep 1 # Hold for a second
    git push origin --delete "${local_target_branch_name}" # Delete remote branch
  fi
done
David Kariuki
  • 1,522
  • 1
  • 15
  • 30
0

My solution:

  1. edit ~/.zshrc.
  2. add the following code.
...
# delete local branches that have not been updated within the last 3 months.
deleteStaleBranches() {
# Step 1: Get the list of branches and their last commit dates
TZ='Asia/Shanghai' git for-each-ref --sort=-committerdate --format='%(refname:short) %(committerdate:format-local:%Y-%m-%d %H:%M:%S)' refs/heads | while read branch date; do
    # Skip branches with specific names
    if [[ $branch =~ ^(main|master|dev|develop|code-review)$ ]]; then
        continue
    fi

    # Step 2: Identify branches not updated within the last 3 months and delete them
    cutoff_date=$(TZ='Asia/Shanghai' date -v-3m +%Y-%m-%d)
    if [[ $date < $cutoff_date ]]; then
        echo "Branch '$branch' last updated on $date (older than 3 months)."
        # Uncomment the next line to delete the branch
        git branch -D "$branch"
    fi
done


}

# Delete remote branches that have not been updated within the last 3 months.
deleteStaleRemoteBranches() {
    # Step 1: Get the list of remote branches and their last commit dates
    git ls-remote --heads origin | while read sha ref; do
        # Extract the branch name from the full ref name
        branch=$(basename "$ref")

        # Skip branches with specific names
        if [[ $branch =~ ^(main|master|dev|develop|code-review)$ ]]; then
            continue
        fi

        # Step 2: Get the last commit date for the remote branch
        date=$(TZ='Asia/Shanghai' git show --format="%ci" "$sha" | head -n 1)

        # Step 3: Identify branches not updated within the last 3 months and delete them
        cutoff_date=$(TZ='Asia/Shanghai' date -v -3m "+%Y-%m-%d" 2>/dev/null || python -c "from datetime import datetime, timedelta; print((datetime.now() - timedelta(days=90)).strftime('%Y-%m-%d'))")
        if [[ "$date" < "$cutoff_date" ]]; then
            echo "Remote branch '$branch' last updated on $date (older than 3 months)."
            # Uncomment the next line to delete the remote branch
            git push origin --delete "$branch"
        fi
    done
}
...
  1. save file and run source ~/.zshrc.
  2. just run $ deleteStaleBranches or $ deleteStaleBranches in your repository.
James Lam
  • 145
  • 2
  • 8