Doing a `git branch` lists a milion of branches on a remote? You have some policies in a team to delete branch after merging, but let's be serious, someone remember about that? You don't have to worry in this article you will find how to write automatic Deleter (as a time trigger Azure Function) of old stale branches from AzureDevops.
In every project which I worked, I encounter the same problem. After some time there were a lot of branches on the origin. That causes a not necessary use of space and problems when searching for the right branch. Because of that, I decided to write a utility tool which would fix that issue.
The main purpose of the application was to delete regularly all branches from all projects and repositories in our AzureDevops space. Those branches have to meet the following requirements to be deleted:
a branch should be older than 1 month;
a branch should not have any open pull requests;
the branch name is not equal to test, nor beta, nor release.
One of the requirements was to run the code responsible for deletion periodically. I decided to create an azure function in F# which would be triggered by time. The time when the function would be trigger would be a 6 am every day.
I started by creating an Azure function of type TimeTrigger, but it is not so obvious to create a function like that in F#. First of all, you have to create a TimeTrigger function in C# from Visual Studio Code like that:
Or from Visual Studio like that:
Then you have to change an extension of a project file from .csproj to .fsproj beyond that, we want to explicitly set the value of FSharp.Core dependency so we set it to 4.7.0. Next thing is to delete a .cs file which is not needed here and adds a new .fs file to the project. Content of this file should look like this:
We have a ready empty project, so we could go further, to some logic. Because our repositories are located on AzureDevops I used the following library to get information about projects/pull requests/branches etc.: Microsoft.TeamFoundationServer.ExtendedClient but if you like the idea it wouldn’t be a problem to switch usage of this library to one that would suit your needs.
After the installation of the library, the first thing to do would be to download all repositories and get all branches for them.
As you may see the first thing we do here is to create a connection to AzureDevops with all data needed. Then we want to get a git client from the already created connection object. Then in an async block, we could get all repositories via method GetRepositoriesAsync.
Right now we have all repositories, the next step would be to download all branches for these repositories. Branch information for a repository could be achieved by this method:
So as you may notice we used GetRefsAsync method to download information about all branches in a single repository. Next thing is to delete all branches in a repository. To delete a branch we have to create a delete object which should be created concretely:
OldObjectId should be set to the actual ObjectId from ref;
NewObjectId should be equal to 00000000
Name should be set to value from ref;
RepositoryId should be equal to repository id.
To proceed with those requirements I wrote the following code:
I think there is no need to going into this code, as long as it simply creates an object with valid property values. But as long as we have or know how to create a ref to delete we could use a function named UpdateRefsAsync which would delete branches. The whole combined code to fetch branches and delete them at the end looks like this:
We could finish here, but we have to remember that we have some requirements about the branches that should be deleted. The first requirement was to not delete branches from which we have open pull requests. So to download all of those pull requests and get branches from which they were created we run following code:
This method simply downloads all branches specified by criteria, where criteria look like this:
Beyond branches that have opened pull requests, we also want to filter those which were not older than one month or have concrete names. So code like this was written:
At first, we set the month ago date, then for which branch that was passed to a method as a GitRef seq we:
check if the branch is named the same as those branches we don’t want to delete (test/beta/release);
check if the branch resides on a list of branches that have opened pull requests;
check if the branch was updated after a month ago.
Otherwise, we want to delete a branch.
To sum up, what we have right now, we have filtering, gathering information about pull requests and branches. So the combination of all of the above looks like this:
You may notice that we have to add here additional check if the list of branches we want to delete is not empty if it is we don’t want to do any action.
Going further we connect this code with a code responsible for fetching repositories.
We run this code in our time trigger method:
The last thing we have to do is a deployment to Azure. So we set-up an AzureFunction on AzureDevops:
We go to the project and click publish on it. Set all the information and click publish.
If we enable AppInsights we could also check if our function is working:
To summarize, thanks to the above code we could keep our codebase in terms of branches in a good shape, without any irrelevant branches that reside in repositories for months or even years. Because someone forgot to check a checkbox to delete his branch after a merge of a pull request. You could found the full source code here.