the_drow
the_drow

Reputation: 19181

How do I remove all stale branches from Github?

We have a lot of branches that are inactive (the newest is 7 months old, the oldest is two years ago).
I'd like to remove all of those branches in bulk from the remote if no PR is still open for them.

Should I be using Github's API? Should I be using git using snippets like those provided in this StackOverflow question?
Is there some Github functionality I'm not familiar with that can help organize our repository?

Upvotes: 40

Views: 34711

Answers (7)

Enigmatic
Enigmatic

Reputation: 4148

As of 2025, none of the existing answers worked for me; I suspect GitHub has updated the element IDs.

I was able to get it working by selecting the .octicon-trash element and finding the "closest button".

async function deleteStaleBranches(delay = 500) {     
    var stale_branches = document.querySelectorAll('.octicon-trash');

    for (var i = 0; i < stale_branches.length; i++) {  
        stale_branches[i].closest('button').click();
        await new Promise(r => setTimeout(r, delay));     
    }

    const nextPage = document.querySelector('[aria-label="Next Page"]');
    if (nextPage) {         
        console.log("Moving to next page...");
        nextPage.click();
        
        await new Promise(r => setTimeout(r, 2000)); 
        await deleteStaleBranches(delay);
    } else {
        console.log("No more pages left.");
    }
}

deleteStaleBranches(500);

Upvotes: 2

Surya
Surya

Reputation: 2699

You can right click on the browser after opening https://github.com/someowner/someproject/branches/stale and run this in the browser java script console.

To delete all the stale branches. i.e) this script automatically clicks to the next page and proceeds to delete stale branches in the next page.

async function deleteStaleBranches(delay=500) {     
    var stale_branches = document.querySelectorAll('[aria-label="Delete branch"]');
        for (var i = 0; i < stale_branches.length; i++)     {  
          stale_branches.item(i).click();
          await new Promise(r => setTimeout(r, delay));     
        }     
        const next = document.querySelector('[aria-label="Next Page"]');
        if(next) {         
            next.click();
            setTimeout(() => deleteStaleBranches(500), 500);
        } 
}  (() => { deleteStaleBranches(500); })();

To delete the stale branches that is present in the current page only:

async function deleteStaleBranches(delay=500) {     
    var stale_branches = document.querySelectorAll('[aria-label="Delete branch"]');
        for (var i = 0; i < stale_branches.length; i++)     {  
          stale_branches.item(i).click();
          await new Promise(r => setTimeout(r, delay));     
        }     
        
}  (() => { deleteStaleBranches(500); })();

Upvotes: 44

Justin Torre
Justin Torre

Reputation: 125

Step 1 go to stale branches

https://github.com/<space>/<repo>/branches/stale

Step 2 Run this script in the javascript console

const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));

async function clickEvenElements() {
  const elements = Array.from(document.querySelectorAll('.fakZEC')).reverse();
  
  for (let index = 0; index < elements.length; index++) {
    // Since we reversed the array, we need to adjust the index check
    // We check for odd index because the elements are in reverse order
    if (index % 2 === 1) {
      elements[index].click(); // Perform the click action
      await sleep(500); // Sleep for 1000ms or 1 second
    }
  }
}
async function doAll(x) {
  for (let index = 0; index < x; index++) {
    await clickEvenElements();
    document.querySelectorAll(".chXkIK")[1].click()
    await sleep(1000)
  }
}
doAll(1)

Change the number in doAll to how ever many pages you want to recur through

https://gist.github.com/chitalian/6220b31cf8b7dc011220e62f89f0a8e0

Upvotes: -1

Farhad Khan
Farhad Khan

Reputation: 139

I used the following in the console to delete only merged and closed branches.

async function deleteMergedBranches(delay=500) {
    var stale_branches = document.getElementsByClassName('State--merged');
    for (var i = 0; i < stale_branches.length; i++)
    {
        stale_branches[i].parentElement.nextElementSibling.nextElementSibling.nextElementSibling.querySelector('.js-branch-delete-button').click()
        await new Promise(r => setTimeout(r, delay));
    }
}

(() => { deleteMergedBranches(500); })();
async function deleteClosedBranches(delay=500) {
    var stale_branches = document.getElementsByClassName('State--closed');
    for (var i = 0; i < stale_branches.length; i++)
    {
        stale_branches[i].parentElement.nextElementSibling.nextElementSibling.nextElementSibling.querySelector('.js-branch-delete-button').click()
        await new Promise(r => setTimeout(r, delay));
    }
}

Upvotes: 1

Mikhail Fiadosenka
Mikhail Fiadosenka

Reputation: 321

More safe option - delete all merged branches:

async function deleteStaleBranches(delay=500) {
    let buttons = [];
    document.querySelectorAll(".State.State--merged").forEach((el) => {
        const butEl = el.parentElement.nextElementSibling.nextElementSibling.querySelector("button");
        buttons.push(butEl);
    });
    for (let i = 0; i < buttons.length; i++)
    {
        buttons[i].click();
        await new Promise(r => setTimeout(r, delay));
    }
}

(() => { deleteStaleBranches(500); })();

Upvotes: 1

Neil C. Obremski
Neil C. Obremski

Reputation: 20244

I took a different tack from the other answers and just used good ol' git (and bash) to do the work:

  1. Retrieve list of all branches using git branch -r >~/branches.txt (after setting git config --global core.pager cat)
  2. Prune ~/branches.txt of ones I want to keep
  3. Call git push <ref> --delete <branch> on each one ...
for ref_branch in $(cat ~/branches.txt | head -n 5); do
    ref="$(echo "$ref_branch" | grep -Eo '^[^/]+')"
    branch="$(echo "$ref_branch" | grep -Eo '[^/]+$')"
    git push "$ref" --delete "$branch"
done

Note the use of | head -n 5 to give it a try with only 5 at a time. Remove that to let the whole thing rip.

This will likely work on most sh shells (zsh, etc.) but not Windows; sorry.

Upvotes: 4

kfb
kfb

Reputation: 6532

You can certainly achieve this using the GitHub API, but you will require a little bit of fiddling to do it.

First, use the list pull requests API to obtain a list of open pull requests. Each item in this list contains a ["head"]["ref"] entry which will be the name of a branch.

Now, using the get all references API, list all of the references in your repository. Note that the syntax for branches in the Git Data API is slightly different than the one returned from the pull request API (e.g. refs/heads/topic vs. topic), so you'll have to compensate for this. The references API also returns tags unless you search just the refs/heads/ sub-namespace, as mentioned in the docs, so be aware of this.

Once you have these two lists of branch refs, it's simple enough to work out which branches have no open pull requests (don't forget to account for master or any other branch you wish to keep!).

At this point, you can use the delete reference API to remove those branch refs from the repository.

Upvotes: 12

Related Questions