Writing blogs is not a one-time thing. Maybe sometime after you posted a blog, you find a typo, or you get some feedback from your readers and want to further elaborate on some paragraph in your blog, and so on. So keep a revision history for each post is a good idea, not only for you, but also for your readers, to let them know that you're keep polishing your blogs.

However, doing this manually is kind of tedious, especially when you made multiple changes you want to show. Fortunately, you use static site generator (like Jekyll or Octopress) and use git to manage your content. (What? You don't? The I feel sad for you :-) So why don't just show the git revision history for that blog? This is the octopress-post-revision comes for.

If you feel interested, please refer to the README page on how to install this plugin and how to configure it. This post will give a detailed description on how this plugin works.

The idea is simple, yet implementing it is not trivial. It's more difficult for me since this is my first time trying to write some code in ruby... But let's break down the task into pieces ant tackle them one by one.

Get Post's Path On You Local File System

We need these information since we need to do a git log on those files. Jekyll provides the Generator interface which allows us to generate extra information, which is exactly what we want.

We need three piece of information:

  • Post file's full/absolute path
  • Post file name
  • Post file's dir name

The last two information are used to generate the View on Github link.

This is what the PostFullPath looks like.

class PostFullPath < Generator
    safe :true
    priority :high

    # Generate file info for each post and page
    #  +site+ is the site
    def generate(site)
      site.posts.each do |post|
        base = post.instance_variable_get(:@base)
        name = post.instance_variable_get(:@name)
        post.data.merge!({
          'dir_name' => '_posts',
          'file_name' => name, 
          'full_path' => File.join(base, name),
        })
      end
      site.pages.each do |page|
        base = page.instance_variable_get(:@base)
        dir = page.instance_variable_get(:@dir)
        name = page.instance_variable_get(:@name)
        page.data.merge!({
          'dir_name' => dir,
          'file_name' => name, 
          'full_path' => File.join(base, dir, name)})
      end
    end
end

The Post class has several instance variables (e.g., @base, @name) that has the file information, yet how to get them outside the class got me. After Google a bit, this thread gives me the solution, i.e., the instance_variable_get method.

Another thing to note is the dir_name, since Jekyll assumes post files are put in the _post directory, so we can hard code post['dir_name'] as _posts. Yet for pages, we need the real dir name.

The revision Liquid Tag

Once we got the file information, we can use git to get the change history of that file. We also need to format the logs for display purpose.

Here is the code that fetch logs from git:

cmd = 'git log --date=local --pretty="%cd|%s" --max-count=' + @limit.to_s + ' ' + full_path
logs = `#{cmd}`

We specify the date format as local, and the log message as customized format. %cd means commit date, and %s is the subject. We also limit the number of logs, in case you get to many commit on on post.

The View on Github Link

Since we only display the latest @limit number of commit, we provide the View on Github link which links to the Github's commit history page. The format of the URL is

https://github.com/<user>/<repo>/commits/<branch>/<file_path>

Here is the code that get branch information.

cmd = 'git rev-parse --abbrev-ref HEAD'
# chop last '\n' of branch name
branch = `#{cmd}`.chop

Now we have all the information we need, and here is how we compose the final URL.

link = File.join('https://github.com', site['github_user'], site['github_repo'],
                 'commits', branch, site['source'], post['dir_name'], post['file_name'])