In case you’ve been living under a programming rock, Ctags (specifically
Exuberant Ctags, not the BSD version
shipped with OS X) indexes source code to make it easy to jump to
functions, variables, classes, and other identifiers in (among other
editors) Vim (see :help tags). The major downside to Ctags is having
to manually rebuild that index all the time. That’s where the
not-so-novel
idea of
re-indexing from various Git commit hooks comes in.
Git hooks are repository specific.
Some
would recommend using a script to install said hooks into a given
repository. But for me, that’s too manual. Let’s set up a default set
of hooks that Git will use as a template when creating or cloning a
repository (requires Git 1.7.1 or newer):
Now onto the first hook, which isn’t actually a hook at all, but rather
a script the other hooks will call. Place in
.git_template/hooks/ctags and mark as executable:
March 2020 edit: Changed to generate tag file in work tree, not Git dir, because
Fugitive no longer provides built-in support for the latter.
Making this a separate script makes it easy to invoke .git/hooks/ctags
for a one-off re-index (or git config --global alias.ctags
'!.git/hooks/ctags', then git ctags), as well as easy to edit for that
special case repository that needs a different set of options to
ctags. For example, I might want to re-enable indexing for JavaScript
or SQL files, which I’ve disabled here because I’ve found both to be of
limited value and noisy in the warning department.
Here come the hooks. Mark all four of them executable and place them in
.git_template/hooks. Use this same content for the first three:
post-commit, post-merge, and post-checkout (actually my
post-checkout hook includes hookup
as well).
I’ve forked it into the background so that my Git workflow remains as
latency-free as possible.
One more hook that oftentimes gets overlooked:
post-rewrite. This is fired after git commit --amend and
git rebase, but the former is already covered by post-commit.
Here’s mine:
Once you get this all set up, you can use git init in existing
repositories to copy these hooks in.
So what does this get you? Any new repositories you create or clone
will be immediately indexed with Ctags and set up to re-index every time
you check out, commit, merge, or rebase. Basically, you’ll never have
to manually run Ctags on a Git repository again.
Update: hookup does all this for
you, plus migrates your database!
I recently discovered RVM’s ability to
create a project .rvmrc when running rvm use --rvmrc. Commented out
by default is a section for automatically running Bundler when cding
to your project:
I excitedly enabled this, as the less I have to run the bundle command
manually, the better. But it quickly became apparent that this is the
wrong time to bundle. Nothing changes when I cd into a project, so
why would the bundle be out of date? Except after the initial clone,
this trick did nothing but eat valuable seconds (sometimes minutes).
What I really needed was a way to bundle right after a git checkout or
git pull --rebase (or throughout a git bisect, if we want to get
really ambitious).
As luck would have it, Git provides a post-checkout hook that runs in
all those cases. Let’s take a stab at it, shall we? Create
.git/hooks/post-checkout with the following content and chmod +x it:
Awesome? You bet, but it runs on every HEAD switch, which means we
still lose valuable seconds even when the Gemfile isn’t updated.
Luckily, the post-checkout hook receives the old and new HEAD as
arguments, so it’s easy to check for changes to the Gemfile,
Gemfile.lock, or the project’s gem spec:
What else? Well, we could silence some of the noise like RVM does:
Tired of conflicts on your schema file that boil down to the version
specification at the top? Me too. After reading Will Leinweber’s
article on resolving Gemfile.lock conflicts
automatically,
I decided I could do better. I started with a
tweet and a
gist, but after seeing the reception it
received, I’m expanding it to a full-on blog post.
The snippet below goes in your ~/.gitconfig, and is basically a simple
algorithm that resolves a schema version conflict by picking the higher
number. Personally, I’d rather have an ugly mess in my Git config file
than a second file I have to copy around everywhere or a gem I have to
install.
[merge "railsschema"]
name = newer Rails schema version
driver = "ruby -e '\n\
system %(git), %(merge-file), %(--marker-size=%L), %(%A), %(%O), %(%B)\n\
b = File.read(%(%A))\n\
b.sub!(/^<+ .*\\nActiveRecord::Schema\\.define.:version => (\\d+). do\\n=+\\nActiveRecord::Schema\\.define.:version => (\\d+). do\\n>+ .*/) do\n\
%(ActiveRecord::Schema.define(:version => #{[$1, $2].max}) do)\n\
end\n\
File.open(%(%A), %(w)) {|f| f.write(b)}\n\
exit 1 if b.include?(%(<)*%L)'"
Now all that’s left is to tell Git to use that algorithm for
db/schema.rb. You have to do this on a per project basis. You can
either commit it to .gitattributes or keep it locally in
.git/info/attributes.
db/schema.rb merge=railsschema
That’s it. I haven’t beat on it too hard yet, but it’s handled simple
conflicts beautifully.
I’ve moved my blog to Jekyll. I think this is the post where I’m
supposed to apologize for falling off the blog wagon and promise to post
more in the future, though truth be told I have few regrets and make no
promises. My aged Drupal install had gotten to the point where I felt
actively discouraged from posting. Now that I’ve rectified that, I’ve
at least enabled myself to post in the future if I have any flashes of
inspiration. Still, I promise nothing. I only migrated the handful of
posts that seemed less than completely obsolete.
My migration process produced two noteworthy artifacts: Vim syntax
highlighting for Liquid and the
same for Markdown. There are
existing implementations of both of these, but they had limitations I
could not accept (most notably, I couldn’t combine them). The Liquid
set has some Jekyll specific goodies like YAML front matter highlighting
and support for Pygments highlight blocks. You need to explicitly
specify which types of highlight blocks you want highlighted and map
between the Pygments type and Vim type in your vimrc: