Travis CI Status in Shell Prompt

Travis CI Status in Shell Prompt
Photo by James Harrison / Unsplash

Update:

I've updated the build status checking script, because updating all branches was taking too long (over 20 minutes.)
Now it performs frequent updates for the current branch, and only periodic updates for all branches.


Travis CI is a distributed continuous integration service for the open source community, and it can be used with any of your public projects on GitHub. You've probably seen some 'build status' badges on GitHub repos.

Wouldn't it be cool if you could see that build status in your shell prompt whenever you are working on a project?

Here's what my shell prompt looks like now:

Travis CI status in prompt

This shows the build status for the current branch.


You will need to cache the build status, since looking it up takes a few seconds.

You should use my fork of mislav's travis-ci script, which can check the build status of a project. Make sure ~/bin is in your PATH, and if you are using RVM, make sure you are using your default ruby.

Run the following to install it:

mkdir -p ~/bin/
curl -sL https://raw.github.com/gist/1708408/travis.rb > ~/bin/travis-ci \
 && chmod +x ~/bin/travis-ci

gem install hub | tail -2
ruby -e 'require "json"' 2>/dev/null || gem install json

Next, we need to update the cached status.
The following code is included as part of my SCM Breeze project, but feel free to save the update_travis_ci_status script at the bottom of this post [1] to /usr/bin/update_travis_ci_status.

We will also need a way to run this update task every few minutes, across all of our local git repos.
We only want to frequently update the status for the currently checked out branch, and periodically update the status for all branches.

The SCM Breeze project also maintains an index of your git repositories, which gives you the ability to run batch commands via the git_index function.
So the build status update can be easily set up as a cron task:

_/5 _ \* \* _ /bin/bash -c '. $HOME/.bashrc && git_index --rebuild && git_index --batch-cmd update_travis_ci_status'
_/45 \* \* \* \* /bin/bash -c '. $HOME/.bashrc && git_index --rebuild && export UPDATE_ALL_BRANCHES=true && git_index --batch-cmd update_travis_ci_status'

Alternatively, you could save the following script to /usr/bin/update_all_travis_ci_statuses.

#!/bin/bash

# (Replace `$HOME/code` with the location of your projects)

for f in find "$HOME/code" -maxdepth 4 -name .travis.yml; do
  (builtin cd "$(dirname $f)" && update_travis_ci_status)
done

... and use the following cron task:

_/5 _ \* \* _ /bin/bash -c '. $HOME/.bashrc && /usr/bin/update_all_travis_ci_statuses'
_/45 \* \* \* \* /bin/bash -c '. $HOME/.bashrc && export UPDATE_ALL_BRANCHES=true && /usr/bin/update_all_travis_ci_statuses'

(you need to source your .bashrc if your default ruby comes from RVM)

Finally, you need a way to display the cached status in your prompt.

Here are the functions I use to return a colored pass / fail / running symbol:


# Returns the Travis CI status for a github project

parse_travis_status() {
local branch="$1"
  if [ -z "$branch" ]; then branch="master"; fi

local stat_file=$(find_in_cwd_or_parent ".travis_status~")
  if [ -e "$stat_file" ]; then
case "$(grep -m 1 "^$branch " "$stat_file")" in
*passed) echo "\[\e[01;32m\]✔ ";; # green
*failed) echo "\[\e[01;31m\]✘ ";; # red
\*running) echo "\[\e[01;33m\]⁇ ";; # yellow
esac
fi
}

# Test whether file exists in current or parent directories

find_in_cwd_or_parent() {
local slashes=${PWD//[^\/]/}; local directory=$PWD;
for (( n=${#slashes}; n>0; --n )); do
    test -e $directory/$1 && echo "$directory/$1" && return 0
    directory=$directory/..
done
return 1
}

(it also works if you are in a project's sub-directory.)

Finally, add $(parse_travis_status "$current_branch") somewhere in your $PS1. You should set the $current_branch variable to the current git branch, but it defaults to the master branch if you leave it blank.

You may like to have a look at the prompt section of my dotfiles, to see how I do it.

Enjoy! Please let me know if you have any questions, or need some help.


[1] update_travis_ci_status script:

#!/bin/bash
if [ -e ".travis.yml" ]; then
if type ruby > /dev/null 2>&1 && type travis-ci > /dev/null 2>&1; then
local stat_file=".travis_status~"
local tmp_stat_file="$stat_file"".tmp"

    # Either update all branches, or only current branch
    if [ "$UPDATE_ALL_BRANCHES" = "true" ]; then
      local all_branches=$(\git branch -a)
      # All branches on origin remote that have local copies
      local branches=$(comm -12 <(echo "$all_branches" | \
                                  sed "s/ *remotes\/origin\///;tm;d;:m;/^HEAD/d;" | sort) \
                                <(echo "$all_branches" | \
                                  sed "/ *remotes\//d;s/^[\* ]*//" | sort))
      # Create a new, blank temp file
      echo -n > "$tmp_stat_file"
    else
      # Only current branch
      local branches="$(\git branch 2> /dev/null | sed "s/^\* \([^ ]*\)/\1/;tm;d;:m")"
      # Copy current file to temp file
      touch "$stat_file"
      cp -f "$stat_file" "$tmp_stat_file"
    fi

    for branch in $branches; do
      local travis_output=$(travis-ci "$branch" 2>&1)
      local status=""
      case "$travis_output" in
      *built\ OK*)    status="passed";;
      *failed*)       status="failed";;
      *in\ progress*) status="running";;
      esac

      # If branch has a build status
      if [ -n "$status" ]; then
        if grep -q "^$branch" "$tmp_stat_file"; then
          # Replace branch's build status
          sed -e "s/^$branch .*/$branch $status/" -i "$tmp_stat_file"
        else
          # Append new line for branch
          echo "$branch $status" >> "$tmp_stat_file"
        fi
      fi
    done

    # Replace current stat file with finished update
    mv -f "$tmp_stat_file" "$stat_file"
    # Ignore status file from git repo
    if ! ([ -e .git/info/exclude ] && grep -q "$stat_file" .git/info/exclude); then
      echo "$stat_file" >> .git/info/exclude
    fi

fi
fi