Using GNU Global with C++ and Git

By Christian Stigen Larsen
Posted 23 Feb 2016 — updated 24 Feb 2016

GNU global is a very compelling way to quickly search your project from the command line. Here I show how to index your code and update it automatically when using git.

Indexing your code

To index your code, simply run the following in your project root directory:

$ gtags

This will parse your code for tags and create the database files GPATH, GRTAGS and GTAGS. It's blazing fast, too: A project with several million lines of code took only seven seconds on my workstation.

For C++ projects, note that gtags may interpret .h files as C code. You can fix that up with ~/.globalrc, but so far I haven't had any big problem with that.

Querying the code

Now that you have indexed your project, you can query for specific tags by typing global name. For example, to see the definition for value_t, just type

$ global value_t
lib/optional/libffi/ffi.cpp

Add -x to get a code excerpt:

$ global -x value_t
value_t            28 lib/optional/libffi/ffi.cpp struct value_t {

To see where value_t is referenced, add -r:

$ global -xr value_t
value_t           415 lib/optional/libffi/ffi.cpp   value_t *retval = new value_t(size);
value_t           463 lib/optional/libffi/ffi.cpp   value_t* value = static_cast<value_t*>(car(p)->pointer->value);
value_t           471 lib/optional/libffi/ffi.cpp   value_t* value = static_cast<value_t*>(car(p)->pointer->value);
value_t           479 lib/optional/libffi/ffi.cpp   value_t* value = static_cast<value_t*>(car(p)->pointer->value);
value_t           487 lib/optional/libffi/ffi.cpp   value_t* value = static_cast<value_t*>(car(p)->pointer->value);
value_t           495 lib/optional/libffi/ffi.cpp   value_t* value = static_cast<value_t*>(car(p)->pointer->value);
value_t           503 lib/optional/libffi/ffi.cpp   value_t* value = static_cast<value_t*>(car(p)->pointer->value);
value_t           511 lib/optional/libffi/ffi.cpp   value_t* value = static_cast<value_t*>(car(p)->pointer->value);

You can also use -g to grep, so you don't have to spell out the correct name:

$ global -xg make_clo
make_clo           34 include/mickey/eval.h cons_t* make_closure(cons_t* args, cons_t* body, environment_t* e);
make_clo          371 src/core-transition.cpp    * (or we could call make_closure here):
make_clo          218 src/eval.cpp               cons_t *closure = make_closure(def_args, def_body, e->extend());
make_clo          272 src/eval.cpp             return make_closure(args, body, e->extend());
make_clo          613 src/library/scheme-base.cpp    * (or we could call make_closure here):
make_clo           60 src/library/srfi-16.cpp   return make_closure(symbol("args"), cons(cond_cases), e);
make_clo           14 src/make-closure.cpp cons_t* make_closure(cons_t* args, cons_t* body, environment_t* e)

You can also use regular expressions with -e. See man global for many other options.

Pro tip: Create an alias for global:

$ alias gx='global -x'
$ gx main
main              144 src/mickey.cpp   int main(int argc, char** argv)
$ gx -r make_closure
make_closure       34 include/mickey/eval.h cons_t* make_closure(cons_t* args, cons_t* body, environment_t* e);
make_closure      218 src/eval.cpp               cons_t *closure = make_closure(def_args, def_body, e->extend());
make_closure      272 src/eval.cpp             return make_closure(args, body, e->extend());
make_closure       60 src/library/srfi-16.cpp   return make_closure(symbol("args"), cons(cond_cases), e);

There is also the gtags-cscope utility. It lets you interactively search for stuff and open an editor. I'm not sure I'll be using it much, but it's there.

Automatically syncing gtags on git updates

At work, I've set up so that the tag-files are automatically updated whenever git changes the files on disk, for example when I fetch and checkout, cherry-pick and so on. I'll show how to do that.

First, add GPATH, GRTAGS and GTAGS to .gitignore:

$ echo "GPATH\nGRTAGS\nGTAGS" >> .gitignore

Next, you need to set up some git hooks. The whole approach here is taken from Effortless Ctags with Git by Tim Pope, and slightly modified to work with GNU global.

Put the below script in .git/hooks/gtags:

#! /bin/sh
set -e
dir="`git rev-parse --git-dir`"
trap 'rm -f "$dir/$$.gtags"' EXIT
mkdir "$dir/$$.gtags"
git ls-files | \
    gtags --file=- --skip-unreadable "$dir/$$.gtags"
mv $dir/$$.gtags/* "$dir/.."

Unfortunately, GNU global doesn't allow relative paths in the environment variable GTAGSROOT, so we can't store the database under .git.

Put the following in post-commit, post-merge, post-checkout:

#! /bin/sh
.git/hooks/gtags >/dev/null 2>&1 &

Notice that it runs gtags in the background, which is nice.

Put this in post-rewrite:

#! /bin/sh
case "$1" in
  rebase) exec .git/hooks/post-merge ;;
esac

For an explanation of the above hooks, look at the post by Tim Pope above. It suggests putting them in a git template directory, which means they are automatically copied over to new or cloned repositories.

Finally, make them executable:

$ chmod +x .git/hooks/gtags \
           .git/hooks/post-commit \
           .git/hooks/post-merge \
           .git/hooks/post-checkout \
           .git/hooks/post-rewrite

Now, whenever you checkout etc., the index will be updated.

As Tim Pope writes, you can even create a command git gtags so you can update on demand:

$ git config --global alias.gtags '!.git/hooks/gtags'

This lets you do

$ git gtags

to manually update the database. However, if you have only changed a few files and want to re-index, you might be better off using global -u to perform an update. It could be faster.