See the History of a Method with git log -L

Git can “trace the evolution” of a specific method when passed -L using the :<funcname> variant. Let’s take a look at the history of this blog’s posts’ published scope, which is used in most places that articles are listed out. You can see that git is able to identify the boundaries of the method even as the length changes and it prints out the full method with diff from each commit in the log, which is filtered to changes to that method.

$ git log -L :self.published:app/models/post.rb -n3
commit d1fbdf3b9a5bd37dd49d61c61ee0f51c75fdb806
Author: Caleb Hearth <caleb@calebhearth.com>
Date:   Thu Feb 2 16:36:34 2023 -0700

    Decree, LFW, new homepage, RSS-only posts

diff --git a/app/models/post.rb b/app/models/post.rb
--- a/app/models/post.rb
+++ b/app/models/post.rb
@@ -15,6 +15,6 @@
   def self.published
     all
-      .reject { _1.frontmatter[:draft] }
+      .reject { _1.frontmatter[:draft] || _1.tags.include?("rss-only") }
       .select { _1.date <= Date.today }
   end


commit 37a830b625db4e60e0ffbfec881d693369706169
Author: Caleb Hearth <caleb@calebhearth.com>
Date:   Fri Nov 12 11:39:48 2021 -0600

    Publish posts published today

diff --git a/app/models/post.rb b/app/models/post.rb
--- a/app/models/post.rb
+++ b/app/models/post.rb
@@ -15,6 +15,6 @@
   def self.published
     all
       .reject { _1.frontmatter[:draft] }
-      .select { _1.date < Date.today }
+      .select { _1.date <= Date.today }
   end


commit b1ed34a897af9ff2f882e140132a6e6045257b10
Author: Caleb Hearth <caleb@calebhearth.com>
Date:   Mon Nov 1 10:33:13 2021 -0500

    Don't filter out all posts

diff --git a/app/models/post.rb b/app/models/post.rb
--- a/app/models/post.rb
+++ b/app/models/post.rb
@@ -12,6 +15,6 @@
   def self.published
     all
-      .reject { |post| post.frontmatter[:draft] }
-      .reject { |post| post.frontmatter[:date] < Date.today }
+      .reject { _1.frontmatter[:draft] }
+      .select { _1.date < Date.today }
   end

If you got excited and tried this on your own, you may have been disappointed to see a message like

fatal: -L parameter 'self.published' starting at line 1: no match

While Git knows how to do this for some languages (mostly the C family), it needs a bit of help for a lot of others including Ruby and Swift. Fortunately it’s as simple as adding one these lines (you only need the one relevant to your project’s language) to your project’s .gitattributes

*.rb diff=ruby
*.swift diff=swift

While Git provides a Ruby diff pattern, if you want to use Swift or other unsupported languages you’ll need to provide your own pattern. To do this, you’ll want to add something like this to your ~/.gitconfig (or .git/config in a project):

[diff "swift"]
xfuncname = ^[ \t]*((class|struct|func)[ \t].*)$

Update Paul Samuels was good enough to reach out to me with a more robust Swift regular expression that takes into account for the static keyword:

[diff "swift"]
xfuncname = ^[ \t]*((class|struct|func|static)[ \t].*)$

It’s a Regex, so if that doesn’t quite fit your needs or if you need to put together an option for another language you can use your regular expression skills to figure that out. You need a pattern that will match the first line of the block you want to display, and Git will show lines up to the next match. This one will match classes, structs, and functions in Swift.

If you’d like this to be global so you don’t get that error in each new project, you can set up a global .gitattributes file for yourself like this:

$ git config --global core.attributesfile ~/.gitattributes

Then add the above lines to ~/.gitattributes.

Webmentions

flanger001

What a cool idea! I never even knew you could do this, and I'm in git log a lot. This goes right in the toolbox. Thanks Caleb!

Very minor side note: I find the font on this blog difficult to read!

jaypeejay

Awesome, I knew this was possible and could have used it on Friday, but couldn’t be bother to look up how to do it in the git CLI