When coding, I often find myself wanting to quickly check the details of a function or method related to what I’m working on. For instance, I might need to understand how the function handles its arguments or recall its side effects.
If you just want to know how to do this, you can jump down and skip my story.
I usually use ctags
with Vim for this. A quick ]
, a brief read, and a <Ctrl-o>
and I’m back from whence I came and more informed for my effort. Still, sometimes would be more convenient to just have the code printed right to my terminal from the command line.
If you’re here, you might remember that we previously looked at Git’s ability to walk the history of a specific method using git log -L
1. What you might not know is that git log
and git show
are close siblings—actually defined in the same source file in the Git codebase. I tried pretty hard to find the right incantation to ask git-show
to display just a specific function. I often use it to show a file at a given commit with git show some-branch:path/to/file
to print a full file at a given commit. I was hopeful that it would have some variant to show what Git calls “hunks”, but despite learning that several Git builtins do indeed have some hidden flags, I just couldn’t make it work.
After that I spent way too long spelunking the Git codebase to try and retrieve the Regex pattern2. git config
won’t give it to you unless it’s one you’ve defined in your own ~/.gitconfig
. For my purposes that wasn’t useful as I let Git use it’s built-in Ruby patterns. I got as far as figuring out that passing git check-attr diff <filename>
will give me the name of the diff Regex that it looks up the actual Regex if I’d defined it in ~/.gitattributes
, which was nice to know but not useful.
Pivoting back, I finally thought to ask git log
to show me the current state of a method by limiting the result to a single commit and setting the format to empty. This approach nearly worked, but it showed the most recent diff of the function, including the diff header:
$ git log -1 -L ":all:app/models/post.rb" --format=
diff --git a/app/models/post.rb b/app/models/post.rb
--- a/app/models/post.rb
+++ b/app/models/post.rb
@@ -8,4 +8,7 @@
def self.all
- @@all ||= super.each(&:validate!).sort_by(&:date).reverse!
+ @@all ||= super
+ .each(&:validate!)
+ .sort_by(&:date)
+ .reverse!
end
I could write a grep
command to give me what I wanted from this output (just the code, without old lines or +
prefixes for changed lines), but something I’d seen while code diving tingled in the back of my mind: the documentation in the Git codebase for the -L
flag is actually written as a file that can be included elsewhere. Tracing that inclusion, I found that it’s also in git-blame.txt, which indeed does accept the -L
flag with the funcname variant (though not with the filename)!
$ git blame -s -L ":\ball\b" app/models/post.rb
94b0b1db0 8) def self.all
b1ed34a89 9) @@all ||= super
b1ed34a89 10) .each(&:validate!)
b1ed34a89 11) .sort_by(&:date)
b1ed34a89 12) .reverse!
94b0b1db0 13) end
94b0b1db0 14)
That output is much easier to clean up with cut
and sed
(the extra lines at the beginning of the git log
and the end of the git blame
output are not accidental in the above snippets, they’re actually printed).
$ git blame -s -L ":\ball\b" "app/models/post.rb" \
# remove the commit hash and line number
| cut -d ')' -f2- \
# remove the inserted leading space, but leave indentation
| cut -d ' ' -f2- \
# remove trailing blank lines
| sed -e :a -e '/^\n*$/{$d;N;};/\n$/ba'
def self.all
@@all ||= super
.each(&:validate!)
.sort_by(&:date)
.reverse!
end
The above won’t work unless you remove those comments.
git blame
supports some pretty neat coloring options that, if specified in your gitconfig
, will cause cut -d ')'
to leave behind some shell escape codes at the beginning of the line. To avoid that, you can pass -c blame.coloring=none
3 to git
as we see here in this extracted shell script which also adds pagination for long functions but will not page if the output fits into your terminal.
All wrapped up in a script:
#!/bin/sh
# usage: git show-func <name> <file>
git \
-c blame.coloring=none \
blame -s --progress \
-L ":\b$1\b" -- "$2" \
| cut -d')' -f2- \
| sed -e :a -e '/^\n*$/{$d;N;};/\n$/ba' \
| less --quit-if-one-screen --RAW-CONTROL-CHARS --no-init
The downside of using git blame
is that if the range of lines in your function has had a lot of history to it, git blame
may take a while to generate the prefixes to each line. That’s unfortunate since I immediately throw out all of the commit and authorship information with cut
. If the slowdown bothers me enough I may spend the time to filter out the git log
cruft instead.
Coming soon: Colorizing that output right to the terminal in your preferred color scheme (as long as your preferred $EDITOR
is nvim
).
-
And if you haven’t read “See the History of a Method with git log -L” and enjoy this post, I bet you’ll like that too. ↩
-
the
xfuncname
used bygit log
to find function definitions ↩ -
You can set config values for individual git commands with
git -c key=value subcommand --other --flags
. It has to come betweengit
and the subcommand likeblame
in this case. ↩