Scatterbrains: git as biographer

This is another of my attempts at keeping track of my day in an orderly fashion. It’s a pretty simple idea. Given that most of of what I do is stored in git repositories, my commit logs are my best bet for seeing what I’ve accomplished each day. I just needed to pull them together and bundle them up without having to think about it. If a lot of your work happens in git repositories and you make frequent commits, this might be of use to you.

This script runs nightly and visits a list of local git repositories to extract a log of any commits for the day. It formats them as Markdown and can log them to Day One or just to a plain text file (single file, appended). There’s an accompanying shell command for easily adding the current directory as a repo to check.

This is what my log looks like in nvALT (with a custom theme):

GitLogger Screenshot

Git notes are included, as is body text of the commit if it exists. Formatting creates an unordered list, and short hashes for the commits are added at the end of the commit message, just in case you need them.

As usual, if you’re interested in trying it, I’m happy to share…

Automatic installation

You can use the following command to automatically install the script and have it run at 11:50pm daily:

/usr/bin/ruby -e "$(/usr/bin/curl -fsSL https://raw.github.com/gist/2633967/ed3f35df05ba12f1ab7176c12ad174d5210b38f1/gitlogger-install.rb)"

Manual install

  • Save the script below (gist link) as gitlogger.rb in a script or bin folder on your drive.
  • Use the variables at the top to set it up for logging to Day One (dayone = true), or enter a path to a text file (textlog = "path/to/text.md"). If you don’t want to use text file logging, just set that option to “false”.
  • If you’re logging to Day One, see this post for more instructions. This script doesn’t need the dayone CLI tool, but if you’re not using iCloud for storing your journal, you’ll need to modify the paths to point to your journal file.
gitlogger.rbraw
#!/usr/bin/env ruby
require 'time'
require 'erb'
require 'cgi'

filename = "~/.gitlogger"
## File format, One per line
# Repo Name:/path/to/base
dayone = false # log to day one? (true or false)
textlog = false # "~/Dropbox/nvALT2.2/GitLogger.md" # set to false to disable

git_user = %x{git config --get user.name}.strip
git_user = ENV['GIT_AUTHOR_NAME'] if git_user == ''
git_user = '.' if git_user == ''

entrytext = ""

File.open(File.expand_path(filename), 'r') do |infile|
  while (line = infile.gets)
    name, path = line.strip.split(':')
    Dir.chdir(path)

    repo_log = ''
    repo_log = %x{git log --first-parent --no-merges --author="#{git_user}" --pretty=format:"%%NEWLINE**[#{name}]** %%%ct%%: %s (%h)%n    %+b%n" --since="yesterday"}.gsub(/%(\d+)%/) { |timestamp|
      timestamp.gsub!(/%/,'')
      Time.at(timestamp.to_i).strftime("%H:%M").gsub(/^0/,'')
    }
    # if no remote repository is specified, `git fetch` will output
    # error messages;  so we silence them
    repo_log = %x{git fetch 2>/dev/null && git log --remotes --first-parent --no-merges --author="#{git_user}" --pretty=format:"* **[#{name}]** %%%ct%%: %s (%h)%n    %+b%n" --since="yesterday"}.gsub(/%(\d+)%/) { |timestamp|
      timestamp.gsub!(/%/,'')
      Time.at(timestamp.to_i).strftime("%H:%M").gsub(/^0/,'')
    } if repo_log == ''
    entrytext += repo_log
  end
end

exit if entrytext.strip == ""

# remove the weird empty lines at the beginning
entrytext = entrytext.gsub(/^\s{4}\n/,"").gsub(/\n{3,}/m,"\n\n")

entries = entrytext.split('%NEWLINE')
output = []

# the first line is always empty gibberish and will be omitted
entries[1..-1].each do |entry|
  entry.gsub!(/^(.+)$/, '    \1')
  entry[0] = "*"
  output << entry
end
entrytext = output.join
entrytext << "\n"

if dayone
  uuid = %x{uuidgen}.gsub(/-/,'').strip
  datestamp = Time.now.utc.iso8601
  starred = false

  dayonedir = %x{ls ~/Library/Mobile\\ Documents/|grep dayoneapp}.strip
  dayonepath = "~/Dropbox/Apps/Day\ One/Journal.dayone/entries/"
  #"~/Library/Mobile\ Documents/#{dayonedir}/Documents/Journal_dayone/entries/"
  entry = CGI.escapeHTML("Git Log #{Time.now.strftime("%Y-%m-%d")}:\n\n#{entrytext}")
  template = ERB.new <<-XMLTEMPLATE
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <key>Creation Date</key>
  <date><%= datestamp %></date>
  <key>Entry Text</key>
  <string><%= entry %></string>
  <key>Starred</key>
  <<%= starred %>/>
  <key>UUID</key>
  <string><%= uuid %></string>
</dict>
</plist>
XMLTEMPLATE

  fh = File.new(File.expand_path(dayonepath+uuid+".doentry"),'w+')
  fh.puts template.result(binding)
  fh.close
  # puts "ENTRY ADDED"
  # puts "------------------------"
  # puts "Time:    " + datestamp
  # puts "UUID:    " + uuid
  # puts "Starred: " + starred.to_s
  # puts "Entry:   " + entrytext
end

if textlog
  entry = "---\n\n### #{Time.now.strftime("%D")}:\n\n#{entrytext}"
  open(File.expand_path(textlog), 'a') { |f|
    f.puts entry
  }
end

The second little script (gist link) is a bash function for adding whatever directory I’m working in to the list that the logger uses to find repos for logging. For consistent results in building the repo list, run the glog command from the base directory of the git repository. Set it once and forget it. You can remove repos from logging by editing the file at ~/.gitlogger. You can also just create that file by hand: each line is a repo, with the title first, followed by a colon, followed by the path.

  • Add this function to your ~/.bash_profile to be able to mark the current directory for logging by typing glog Alias, where “Alias” is the name you want to appear for the repo in your log.
glog.shraw
# Add current folder to ~/.gitlogger with name specified as argument 1
# For use with gitlogger.sh
function glog () {
	(echo "$1:`pwd`";grep -v "`pwd`$" ~/.gitlogger) | sort > ~/.gitlogger.tmp
	mv ~/.gitlogger.tmp ~/.gitlogger
}

Scheduling

To automate the script, I suggest using launchd (the OS X version of cron). Use Lingon or copy the code below into a file called com.yourusername.gitlogger.plist and save it in ~/Library/LaunchAgents/. After creating the file, you’ll want to run launchctl load ~/Library/LaunchAgents/com.yourusername.gitlogger.plist to get it started (or log out and back in, but that takes too long). The code as is will set up the logger to run at 11:50pm every night (in your local time). You’ll want to edit the Label and the ProgramArguments values to match your setup.


<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>Label</key>
	<string>com.brettterpstra.gitlogger</string>
	<key>ProgramArguments</key>
	<array>
		<string>/usr/bin/ruby</string>
		<string>/Users/ttscoff/scripts/gitlogger.rb</string>
	</array>
	<key>StartCalendarInterval</key>
	<dict>
		<key>Hour</key>
		<integer>23</integer>
		<key>Minute</key>
		<integer>50</integer>
	</dict>
</dict>
</plist>

Maybe this will be of use to somebody. Code contributions and suggestions welcome, just follow the links on the gists above to fork. My personal git workflow works well with this logging statement, but you might want to modify it to log tags only, etc. Let me know what you do with it!

Brett Terpstra

Brett is a writer and developer living in Minnesota, USA. You can follow him as ttscoff on Twitter, GitHub, and Mastodon. Keep up with this blog by subscribing in your favorite news reader.

This content is supported by readers like you.

Join the conversation