Use script to batch delete remote branches

Foreword

At the end of each iteration, they will not take the initiative to delete the branches they created. The warehouse branches have not been cleaned up for several years, resulting in almost 2k branches.

Ideas

1. Use scripts to implement batch deletions without manually executing git command batch operations.

2. Support filter conditions, such as: merged branches, last commit three months ago, branches containing release characters

Version 1

#!/bin/bash

repository_name="$1"
echo "You are about to delete the branch under ["$repository_name"], please find someone else to back it up first to avoid accidental deletion!!!";
echo " "

# Filter conditions and add branches that do not need to be deleted to this array
excluded_branches=("master" "develop")

#Specify the characters that need to be included in the branch name to be filtered (if it is empty, the filtering will not be performed)
excluded_filter="release/1."

#Specify the characters that need to be included in the name of the branch to be deleted (if it is empty, the filtering will not be performed)
# include_filter=""

# Get the seconds of the current system time
current_time=$(date + %s)

# Calculate the timestamp three months ago
three_months_ago=$((current_time - 3600 * 24 * 90))

# Calculate the timestamp two months ago
two_months_ago=$((current_time - 3600 * 24 * 60))

# Calculate the timestamp one month ago
one_months_ago=$((current_time - 3600 * 24 * 30))


# Get all merged remote branches and perform loop processing
for branch in `git branch -r --merged origin/master | grep -v HEAD`; do
    #Extract branch name
    # simple_name=$(echo "$branch" | grep '/' | cut -d'/' -f 2);

    simple_name=$(echo "$branch" | sed 's#.*origin/##')

    #Exclude branches that do not need to be deleted
    excluded_flag="0";
    for excluded_branch in "${excluded_branches[@]}"; do
        if [[ "$simple_name" == "$excluded_branch" ]]; then
            excluded_flag="1";
            break
        fi
    done

    # Determine whether the deletion conditions are met
    if [[ $excluded_flag == "0" ]]; then
        if [[ -n $excluded_filter ]]; then
            if [[ "$simple_name" == *"$excluded_filter"* ]]; then
                continue
            fi
        fi

        # if [[ -n $include_filter ]]; then
        # if [[ "$simple_name" != *"$include_filter"* ]]; then
        # continue
        # fi
        # fi

        # Get the last commit time of the branch
        branch_timestamp=$(git show --format="%at" "$branch" | head -n1)

        # Select based on actual needs Three months ago $three_months_ago Two months $two_months_ago One month ago $one_months_ago Current $current_time
        if [[ $branch_timestamp -lt $three_months_ago ]]; then
            echo "Deleting branch: $simple_name"

            # Delete local branch
            # git branch -d "$simple_name"

            # Delete remote branch
            git push origin --delete "$simple_name"

            echo "Branch $simple_name deletion completed"
            echo ""
        fi
    fi
done
echo "clear end"

Problem 1: After deleting a branch, there will be inconsistencies between the local and remote ends, resulting in the deletion of non-existent branches (which cannot be solved through pull)

Reason: After deleting a branch, there will be inconsistencies between the local and remote ends, resulting in deletion of non-existent branches (cannot be solved through pull)

Solution: Add the update remote and local reference command

https://blog.csdn.net/BryantLmm/article/details/85130091

Git – git-remote Documentation

Version 2

Added update remote and local reference commands

#!/bin/bash

repository_name="$1"
echo "You are about to delete the branch under ["$repository_name"], please find someone else to back it up first to avoid accidental deletion!!!";
echo " "

# Filter conditions and add branches that do not need to be deleted to this array
excluded_branches=("master" "develop")

#Specify the characters that need to be included in the branch name to be filtered (if it is empty, the filtering will not be performed)
excluded_filter="release/1."

#Specify the characters that need to be included in the name of the branch to be deleted (if it is empty, the filtering will not be performed)
include_filter=""

# Get the seconds of the current system time
current_time=$(date + %s)

# Calculate the timestamp three months ago
three_months_ago=$((current_time - 3600 * 24 * 90))

# Calculate the timestamp two months ago
two_months_ago=$((current_time - 3600 * 24 * 60))

# Calculate the timestamp one month ago
one_months_ago=$((current_time - 3600 * 24 * 30))

echo "Update local and remote branch references"

# Update the branch reference first to ensure that the branch information pulled by git branch -r is consistent with the remote warehouse (deleting branches will cause inconsistencies, resulting in unnecessary traversal)
git remote update origin --prune

echo "Update completed, start deleting target branches in batches"

# Get all merged remote branches and perform loop processing
# for branch in `git branch -r --merged origin/master | grep -v HEAD`; do

# Get all remote branches and perform loop processing
for branch in `git branch -r | grep -v HEAD`; do
    #Extract branch name
    # simple_name=$(echo "$branch" | grep '/' | cut -d'/' -f 2);

    simple_name=$(echo "$branch" | sed 's#.*origin/##')

    #Exclude branches that do not need to be deleted
    excluded_flag="0";
    for excluded_branch in "${excluded_branches[@]}"; do
        if [[ "$simple_name" == "$excluded_branch" ]]; then
            excluded_flag="1";
            break
        fi
    done

    # Determine whether the deletion conditions are met
    if [[ $excluded_flag == "0" ]]; then
        if [[ -n $excluded_filter ]]; then
            if [[ "$simple_name" == *"$excluded_filter"* ]]; then
                continue
            fi
        fi

        if [[ -n $include_filter ]]; then
            if [[ "$simple_name" != *"$include_filter"* ]]; then
                continue
            fi
        fi

        # Get the last commit time of the branch
        branch_timestamp=$(git show --format="%at" "$branch" | head -n1)

        # Select based on actual needs Three months ago $three_months_ago Two months $two_months_ago One month ago $one_months_ago Current $current_time
        if [[ $branch_timestamp -lt $three_months_ago ]]; then
            echo "Deleting branch: $simple_name"

            # Delete local branch
            # git branch -d "$simple_name"

            # Delete remote branch
            git push origin --delete "$simple_name"

            echo "Branch $simple_name deletion completed"
            echo ""
        fi
    fi
done
echo "Clear End"

Final version

Plus ask interaction

  • Install inquirer.js

inquirer Chinese documentation | inquirer js Chinese tutorial | parsing | npm Chinese documentation

node executes the script to pass parameters, and supports the passing of empty writing method

Use “” to wrap parameters and not just separate them with spaces.

Node passes in the script, and the shell supports dynamic variable assignment

Node uses esec to execute the script. It cannot output in real time. It will wait for the script to be executed and output the print content at once

How to execute shell commands in nodejs and echo the results in real time?

Child process | Node.js v21.1.0 Documentation

solution:

Replace exec with spawn

Dynamicly pass in pull branch rules

Problem: This way of writing will cause all branches to be printed to the console

solve:

The code is as follows:

const inquirer = require('inquirer');
const { spawn } = require('child_process');


const format = {
  three_months_ago: '90 days ago',
  two_months_ago: '60 days ago',
  one_months_ago: '30 days ago',
  current_time: 'current',
  branch_all: 'All branches',
  branch_merged: 'Mergeed',
  branch_no_merged: 'Not merged',
}
const label = {
  name: 'The current project is:',
  excluded_filter: 'Specify the characters that need to be included in the branch name to be filtered:',
  include_filter: 'Specify the characters that need to be included in the branch name to be deleted:',
  date_range: 'Filter date range:',
  branch_rule: 'Get remote branch range:',
}

const question = [{
  type: 'input',
  message: 'The current project is:',
  name: 'name',
  default: 'ircloud-ydp-web'
},
{
  type: 'input',
  message: 'Specify the characters that need to be included in the branch name to be filtered (if it is empty, the filtering will not be performed):',
  name: 'excluded_filter',
  default: 'release/1.'
},
{
  type: 'input',
  message: 'Specify the characters that need to be included in the name of the branch to be deleted (if it is empty, the filtering will not be performed):',
  name: 'include_filter',
  default: ''
},
{
  type: 'list',
  message: 'Filter date range:',
  name: 'date_range',
  choices: ['three_months_ago', 'two_months_ago', 'one_months_ago', 'current_time'],
  default: 'three_months_ago',
},
{
  type: 'list',
  message: 'Get remote branch range (all, merged, unmerged):',
  name: 'branch_rule',
  choices: ['branch_all', 'branch_merged', 'branch_no_merged'],
  default: 'branch_all',
}]


let answer = {}

function getParams () {
  return inquirer.prompt(question).then(res => {
    console.log(res)
    answer = {...res}
  })
}

function confirmAgain () {
  const str = Object.keys(answer).reduce((prev, curr) => {
    if(['date_range', 'branch_rule'].includes(curr)) {
      prev + = `${label[curr]} ${format[answer[curr]]}; `;
    }else{
      prev + = `${label[curr]} ${answer[curr]}; `;
    }
    return prev;
  }, '')
  return inquirer.prompt([{
    type: 'list',
    message: `${str}, are you sure? `,
    name: 'answer',
    choices: ['yes', 'no'],
    default: 'yes'
  }]).then(res => {
    if (res.answer === 'yes') {
        // let params = Object.values(res).reduce((prev, curr) => {
      // prev + = `"${curr}" `;
      // return prev
      // }, '')
      // console.log(`bash scripts/delBranch.sh ${params}`);
      // exec(`bash scripts/delBranch.sh ${params}`,(error, stdout, stderr) => {
        // console.log(stdout);
        // console.log(stderr);
        // if (error !== null) {
          // console.log(`exec error: ${error}`);
          // }
          // })
      console.log('After obtaining the parameters, start executing the deletion script');
      let paramsArr = Object.values(answer);
      const childProcess = spawn('sh', [`scripts/delBranch.sh`, ...paramsArr]);
      childProcess.stdout.on('data', (data) => {
        console.log(`Script execution output: ${data}`);
      });
      childProcess.stderr.on('data', (data) => {
        console.error(`Script error message: ${data}`)
      })
    } else {
      console.log('terminate execution');
    }
  })
}


async function main() {
  await getParams();
  await confirmAgain();
}

main();
#!/bin/bash

repository_name="$1"
echo "You are about to delete the branch under ["$repository_name"], please find someone else to back it up first to avoid accidental deletion!!!";
echo " "

# Filter conditions and add branches that do not need to be deleted to this array
excluded_branches=("master" "develop")

#Specify the characters that need to be included in the branch name to be filtered (if it is empty, the filtering will not be performed)
# excluded_filter="release/1."
excluded_filter=$(echo ${2})

#Specify the characters that need to be included in the name of the branch to be deleted (if it is empty, the filtering will not be performed)
include_filter=$(echo ${3})

# Get the seconds of the current system time
current_time=$(date + %s)

# Calculate the timestamp three months ago
three_months_ago=$((current_time - 3600 * 24 * 90))

# Calculate the timestamp two months ago
two_months_ago=$((current_time - 3600 * 24 * 60))

# Calculate the timestamp one month ago
one_months_ago=$((current_time - 3600 * 24 * 30))

time_range=$(eval echo '$'${4})

echo "Update local and remote branch references"

# Update the branch reference first to ensure that the branch information pulled by git branch -r is consistent with the remote warehouse (deleting branches will cause inconsistencies, resulting in unnecessary traversal)
git remote update origin --prune

echo "Update completed, start deleting target branches in batches"

branch_all="";
branch_merged="--merged origin/master";
branch_no_merged="--no-merged origin/master";

get_branch_rule=$(eval echo '$'${5})


# Get (all/merged/unmerged) remote branches and perform loop processing
for branch in `git branch -r ${get_branch_rule} | grep -v HEAD` ; do
    #Extract branch name
    # simple_name=$(echo "$branch" | grep '/' | cut -d'/' -f 2);

    simple_name=$(echo "$branch" | sed 's#.*origin/##')

    #Exclude branches that do not need to be deleted
    excluded_flag="0";
    for excluded_branch in "${excluded_branches[@]}"; do
        if [[ "$simple_name" == "$excluded_branch" ]]; then
            excluded_flag="1";
            break
        fi
    done

    # Determine whether the deletion conditions are met
    if [[ $excluded_flag == "0" ]]; then
        if [[ -n $excluded_filter ]]; then
            if [[ "$simple_name" == *"$excluded_filter"* ]]; then
                continue
            fi
        fi

        if [[ -n $include_filter ]]; then
            if [[ "$simple_name" != *"$include_filter"* ]]; then
                continue
            fi
        fi


        # Get the last commit time of the branch
        branch_timestamp=$(git show --format="%at" "$branch" | head -n1)

        # Select based on actual needs Three months ago $three_months_ago Two months $two_months_ago One month ago $one_months_ago Current $current_time
        if [[ $branch_timestamp -lt $time_range ]]; then
            echo "Deleting branch: $simple_name"

            # Delete local branch
            # git branch -d "$simple_name"

            # Delete remote branch
            git push origin --delete "$simple_name"

            echo "Branch $simple_name deletion completed"
            echo ""
        fi
    fi

  done

echo "Clear End"

Add execution command to package.json

“delete-node”: “node scripts/inquirerDelBranch.js”

End

At that time, one click was the most time-saving method