the cover image of blog post Clean up outdated git branches with Babashaka

Clean up outdated git branches with Babashaka

2025-02-23
3 min read

Problem to solve

The pull request is the de-facto way for code collaboration in the multi-member team nowadays.

It's based on git, as git is a distributed version control system so you will not have only one place to manage your code, usually it involves at least two environments, the local repository and the remote one(github or bitbucket for example)

In my team the git flow is roughly like below, it's been proved to be solid enough for quite a lot cross team projects. image

However I have a little annoyning task about branch clean up in the flow. If you are sharp-eyed, you will see after the step 6, the feature branches are automatically deleted once they are merge (by setting the Github preference), the feature branch remains in the local environment, certainly you can remove it with git branch --delete but as programmers we are not good at remembering things, so if it's forgotten there will be outdated branches left in the local git repository.

Again, as programers we are good at automation, so I built a script for the clean up. Bash shall do the work but I haven't used bash for a long time, and sometimes the bash goes too complicated for a simple small task.

Solution

I chose to build a Babasha script, Babash is a lightweight Clojure interpreter for scripting, it exploits the powerful facet of data process from clojure.

image The solution is quiet straightforward, getting both local and remote branches, figure out those only remain in local, they are the potential candiates to be removed. Why I said potential? Because sometimes you will have a diverged branch in your local git environment, for instance, you probably have a feature testing branch that is diverged from the main branch, the script needs to keep these branches untouched. So I add the commits diff check to the flow, i.e. checking the result git log branch_A..branch_B is empyt or not.

Once I get all the outdated branch just iterate them with git branch --delete. Finally I can run the script like this,

# use `:m` to specify the name of main branch $ gbd.clj :m master Branch: [defer-publish] has been removed Branch: [feature-close-connection] has been removed Branch: [feature-produce-retry] has been removed Branch: [feature-singleton-producer] has been removed Branch: [fix-ci-build-error] has been removed

Code

The code of the script is as follows, you can also find it from Github Gist

#! /usr/bin/env bb (require '[babashka.process :refer [shell]]) (require '[clojure.string :refer [includes? split-lines trim]]) (require '[babashka.cli :as cli]) (def cli-options {:main {:default "main" :alias :m :desc "the main branch name" } :help {:coerce :boolean}}) (def cli-opt (cli/parse-opts *command-line-args* {:spec cli-options})) (defn lazy-contains? [col key] (boolean (some #(= % key) col))) (defn behind? [target main] ( -> (shell {:out :string } (format "git log %s..%s" main target)) :out empty?)) (defn not_head [n] (not (includes? n "HEAD"))) (defn git_rm [branch] (shell {:out :string } (format "git branch -d %s" branch)) (println (format "Branch: [%s] has been removed" branch))) (def locals (->> (shell {:out :string } "git branch") :out split-lines)) (def normalized (->> locals (map #(if (includes? % "*") (subs % (count "* ")) %)))) (if (not (lazy-contains? normalized (:main cli-opt))) (println (format "Branch [%s] does not exit" (:main cli-opt) )) (let [remotes_fullname ( -> (shell {:out :string } "git branch -r") :out split-lines ) remote_branches (->> remotes_fullname (filter not_head) (map #(subs (trim %) (count "origin/")))) local_candidates (filter #(not (includes? % "*")) locals) to_remove (filter #(and (not (lazy-contains? remote_branches %)) (behind? % (:main cli-opt))) (map #(trim %) local_candidates)) ] (doseq [to_rm to_remove] (git_rm to_rm) )))
© 2025 Xavier Zhou