Recently I wrote a small command-line tool called gita to view multiple git repos side by side. The source code can be accessed here on github. Its motivation is that I need to compile multiple repos against each other and it is critical to have them on the correct branches. A screenshot is shown below


Here the color codings denote the 5 situations between local and remote branches:

  • white: the local branch has no remote branch.
  • green: the local branch is the same as the remote branch.
  • red: the local branch has diverged from the remote branch.
  • purple: the local branch is ahead of the remote branch (good for push).
  • yellow: the local branch is behind the remote branch (good for merge).

The color choices of purple for ahead and yellow for behind is motivated by blueshift and redshift.

And the extra status symbols have the following meaning:

  • +: staged change exists
  • *: unstaged change exists

This command-line tool also delegates some simple git command such that I don’t need to go into each repo directory to perform them.

One neat python 3.6 feature I discovered working on this side project is the so-called f-string, introduced in PEP-498. It enables embedding of python expressions directly into the string literal, e.g.,

name = 'test-repo'
green = '\x1b[32m'
end = '\x1b[0m'
print(f'{name:<18}{green} master *+ {end}\n')

All you need to do is to place the expression inside braces.

Another small issue I found is that the python package argparse doesn’t work out of box when both nargs='*' and choices are set. For example, I would like to call

gita fetch
gita fetch repo1 repo2

where the no-argument case fetches all registered repos and the with-argument case fetches the specified repos. The following code errors out even if default is set.

p_fetch.add_argument('repo', nargs='*', choices=utils.get_repos())

The underlying problem is that the no-argument case (i.e., repo being empty list) is not in the choices, which feels like a bug to me. I think that case should automatically bypass the choices check. As a workaround, I defined another function utils.get_choices() to add an empty list to the choices.