Using whiptail to show the active git branch for a set of git repositories

Tag
Tag
Published:
Author: Ally

Table of Contents

  1. Preamble
  2. Script (step by step)
  3. Screenshots
  4. Complete Code

Preamble

My day job requires working across multiple repositories. Often more than one repository needs to be on the same branch when I’m working on a feature.

Tangentially, I use tmux and tmuxinator to have a window for each repository and some for monitoring, etc. I often jump between windows (i.e. repositories) and see the active git branch from the command line (I use ohmyzsh with the ys theme).

Install on Mac:

brew install newt

Script (step by step)

1
2
3
4
# list of repository roots
paths=(
    # todo: add your repositories here
)

Here you should add absolute path to repository roots, i.e. "/home/ally/development/ac-website".

6
7
# from the repository roots we will get the branch name
declare -A branches=()

The declare will create a variable named branches, the -A designates this as an associative array/hashmap. We will use this to store the active branch for the repository root.

 9
10
11
12
13
# populate repository roots branches
for p in "${paths[@]}"; do
    k="$(basename "$p")"
    branches[$k]="$(git -C "$p" branch --show-current)"
done

It will look something like this:

$branches = [
    "/home/ally/development/ac-website" => "main"
]

Next we will do some calculations to determine size of the whipatail radio list.

Note: this has zshisms, ${(@k)branches[*]} is different to bash syntax (to list the keys of the array, in bash I believe you would substitute with ${!branches[*]}).

15
16
17
18
19
20
21
items="${#paths[@]}"
height=$((items * 2))

# calculate width of box depending on repository and branch names
w_repo=$(echo "${(@k)branches[*]}" | tr " " "\n" | awk '{ print length($0) }' - | sort -rn | head -n1)
w_branch=$(echo "${branches[*]}" | tr " " "\n" | awk '{ print length($0) }' - | sort -rn | head -n1)
width=$((w_repo + w_branch + 15))

Highlighted lines will (not in a bulletproof manner):

23
24
25
26
27
28
29
30
# make the command (trailing space for concatenating tag item status tuples
cmd="whiptail \
    --title=\"Repository branches\" \
    --backtitle=\"Relevant repository branches\" \
    --ok-button=\"Change Window\" \
    --cancel-button=\"Ok\" \
    --radiolist \"Repository branches\" \
    $height $width $items "

The above might not be syntactically correct (I split across lines for readability).

We will concatenate the tuples for the radio list items.

These come in the format: "tag" "item" status.

32
33
34
35
36
37
38
39
for p in "${(k)paths[@]}"; do
    tag="$(basename "$p")"
    item="${branches[$tag]}"

    cmd+="\"$tag\" "
    cmd+="\"$item\" "
    cmd+="OFF "
done

Note: this has zshisms, ${(@k)paths[@]} is different to bash syntax (to list the keys of the array, in bash I believe you would substitute with ${!paths[@]}).

I tried to iterate through $branches instead of $paths, but for some reason they were in some random order.

41
42
43
# finish the command
cmd+="3>&1 1>&2 2>&3"
answer=$(eval "$cmd")

The 3>&1 1>&2 2>&3 is to grab stderr where the answer if chosen (the tag of the radio list item tuple) is outputted from whiptail without printing to screen.

Using eval is not ideal, but I found it was required.

The following is tmux specific things based on the answer to switch windows.

45
46
47
48
49
50
51
52
53
54
55
if [[ $TERM_PROGRAM == "tmux" ]]; then
    if [[ "$answer" == "ac-website" ]]; then
        tmux select-window -t 0
    elif [[ "$answer" == "ac-skills" ]]; then
        tmux select-window -t 1
    else
        echo "not sure how to handle this answer in tmux"
    fi
else
    echo "not in tmux, not changing window"
fi

Screenshots

With tmux

List of all repositories in your list and the active branch:

whiptail tmux list

Select a branch (or choose Ok to close):

whiptail tmux select

Will change to defined window if in tmux (trust me it does it):

whiptail tmux answer

Without tmux

whiptail list

whiptail answer

Complete Code

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
#!/usr/bin/env zsh
# list of repository roots
paths=(
    # todo: add your repositories here
)

# from the repository roots we will get the branch name
declare -A branches=()

# populate repository roots branches
for p in "${paths[@]}"; do
    k="$(basename "$p")"
    branches[$k]="$(git -C "$p" branch --show-current)"
done

items="${#paths[@]}"
height=$((items * 2))

# calculate width of box depending on repository and branch names
w_repo=$(echo "${(@k)branches[*]}" | tr " " "\n" | awk '{ print length($0) }' - | sort -rn | head -n1)
w_branch=$(echo "${branches[*]}" | tr " " "\n" | awk '{ print length($0) }' - | sort -rn | head -n1)
width=$((w_repo + w_branch + 15))

# make the command (trailing space for concatenating tag item status tuples
cmd="whiptail \
    --title=\"Repository branches\" \
    --backtitle=\"Relevant repository branches\" \
    --ok-button=\"Change Window\" \
    --cancel-button=\"Ok\" \
    --radiolist \"Repository branches\" \
    $height $width $items "

# https://unix.stackexchange.com/a/150041/265713
for p in "${(k)paths[@]}"; do
    tag="$(basename "$p")"
    item="${branches[$tag]}"

    cmd+="\"$tag\" "
    cmd+="\"$item\" "
    cmd+="OFF "
done

# finish the command
cmd+="3>&1 1>&2 2>&3"
answer=$(eval "$cmd")

if [[ $TERM_PROGRAM == "tmux" ]]; then
    if [[ "$answer" == "ac-website" ]]; then
        tmux select-window -t 0
    elif [[ "$answer" == "ac-skills" ]]; then
        tmux select-window -t 1
    else
        echo "not sure how to handle this answer in tmux"
    fi
else
    echo "not in tmux, not changing window"
fi
A Laravel middleware to optimise images with imgproxy on arbitrary markup
Tailwind CSS Breakpoints in Google Chrome Device Toolbar
To bottom
To top
< SM
max-width: 640px