To main content

BetterBranch: A tiny Git helper

Published by Benjamin Marwell on

BetterBranch is a tiny Git helper that lists local branches with ahead/behind counts and the age of each branch tip. I rewrote it in Java and compiled it to a native binary so it stays fast and self-contained.

What does BetterBranch do

BetterBranch has just one job: It shows the list of local branches, but with a few extra pieces of information. It adds two columns »Ahead« and »Behind« in front of the branch name, plus the »last commit« date on that branch.

BetterBranch invocation via the git bb alias
$ git bb
Ahead Behind Branch                                             Last Commit
----- ----- -------------------------------------------------- --------------------
0     0     main                                               3 days, 8 hours ago
0     3     zstd_dist                                          3 days, 9 hours ago
0     3     compress_for_size                                  3 days, 9 hours ago
0     7     fix/build-native-yaml-if                           4 days, 6 hours ago
0     8     fix/workflow-archive-upload                        4 days, 7 hours ago
0     12    fix/build-native-linux-upload                      4 days, 7 hours ago
0     17    fix/early-access-binary-artifacts                  4 days, 10 hours ago
0     19    fix/early-access-artifacts                         4 days, 10 hours ago

I find this super helpful to find branches easily and to see which ones to update or rebase next.

Compared to git branch --list

The command git branch --list gives you only one piece of information: which branches are locally known or checked out. However, the current branch is coloured in green (or whatever colour your terminal supports). It will also list linked worktree branches with a (+) sign, BetterBranch does not do that (yet!), but the invocation of BetterBranch DOES work in worktrees since this week.

Additionally, git branch --list supports a <pattern> argument, something else BetterBranch is still missing.

What BetterBranch does better is to give you an overview of branch relationship: Ahead/Behind and last commit date are very helpful pieces of information not shown when running git branch --list.3 It also sorts by latest tip commit date by default, so the most relevant branches stay near the top. This makes piping through head easy and feasible.

Why the rewrite

I used to use a shell script from a gist. But it was awfully slow.

Since I know Java best and I was looking for another excuse to create a GraalVM native binary, I rewrote it in Java. 😉

Tools used

Since this is a simple tool, I tried to keep the dependencies down as much as possible. However, I did not want to shell out to Git, so I needed Eclipse jGit as a bare minimum. Sadly, Eclipse jGit brought slf4j-api, but that is no big deal. No picocli needed for this tool.

Building with GraalVM

Supported Platforms

To not waste CPU cycles, I decided to adapt my native-build.yaml from jfmt: First, only the linux x64 binary is built. Only when that succeeds all the other platforms are built (Linux:aarch64, Mac:aarch64, Windows:x64).

Signed JARs / Signatures

I came across one problem, though: Signatures from jar archives (»signed JARs«) currently break GraalVM builds. As a workaround, I adapted Copilot’s suggestion to create a shaded jar with all the classes minus the signatures and use that as the only classpath input for GraalVM.

This is how you do it:

Excerpt: Build GraalVM native binary from a shaded JAR
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <!-- other tags omitted for brevity -->
  <build>
    <plugins>
      <!-- other plugins omitted for brevity -->
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-shade-plugin</artifactId>
        <configuration>
          <skip>${native.skip}</skip>
          <shadedArtifactAttached>true</shadedArtifactAttached>
          <shadedClassifierName>native-image</shadedClassifierName>
          <createDependencyReducedPom>false</createDependencyReducedPom>
          <filters>
            <filter>
              <artifact>*:*</artifact>
              <excludes>
                <exclude>META-INF/*.SF</exclude>
                <exclude>META-INF/*.DSA</exclude>
                <exclude>META-INF/*.RSA</exclude>
                <exclude>META-INF/*.MF</exclude>
                <exclude>META-INF/LICENSE.txt</exclude>
                <exclude>META-INF/versions/*/*module-info*.*</exclude>
              </excludes>
            </filter>
          </filters>
        </configuration>
        <executions>
          <execution>
            <id>shade-for-native-image</id>
            <goals>
              <goal>shade</goal>
            </goals>
            <phase>package</phase>
          </execution>
        </executions>
      </plugin>

      <plugin>
        <groupId>org.graalvm.buildtools</groupId>
        <artifactId>native-maven-plugin</artifactId>
        <extensions>true</extensions>
        <configuration>
          <!-- force usage of the shaded jar -->
          <classpath>
            <param>${project.build.directory}/${project.build.finalName}-native-image.jar</param>
          </classpath>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>

Download

Download pre-releases of BetterBranch: BetterBranch early-access @ GitHub