Thursday, May 28, 2009

Random Monotone Hacks

I imagine most people who bother to read this blog know that I am rather fond of Monotone, the distributed version control system (a.k.a. DVCS) that we use to maintain Pidgin's code. One of the most convenient features of monotone is that it's extensible via the Lua scripting language. Over our time using monotone, some of us have come across (or written!) some useful snippets of lua code that can be tossed into ~/.monotone/monotonerc to make things easier on us.

The first such snippet is pretty simple. I have three monotone keys that I use for various purposes. One is for Pidgin development, another is for my work on the Guifications project and the Purple Plugin Pack. The third is for my private projects. Instead of needing to remember to specify which key I want to use in specific circumstances, a small snippet of lua can take care of this for me (of course, anyone wanting to use this will need to edit branch patterns and key names appropriately):

function get_branch_key (branch)
d = { ["im.pidgin"]="rekkanoryo@pidgin.im",
["org.guifications"]="rekkanoryo@guifications.org",
["org.rekkanoryo"]="rekkanoryo@rekkanoryo.org"
}

for k, v in pairs(d) do
if string.find(branch, k) then
return v
end
end
end


Monotone also has the ability to cherry-pick revisions from one branch to transplant into another. (Of course, I recognize that other DVCS tools have this capability too.) To do this, ordinarily we would issue a command like mtn pluck some_revision_id within a working copy of the branch we want to transplant the revision to. In our experience, the default log messages for a pluck leave some to be desired. My fellow developer Sadrul wrote this cool bit to add a pluck-log command to Monotone.

The pluck-log command takes a series of individual revision ID's as arguments. For each revision ID in the list of arguments, the command will execute mtn pluck $REV to grab and apply the changes to the workspace. Additionally, the plucked revision's original changelog entry will be inserted into the changelog for the new commit on the current branch. This is particularly useful if you're creating a new release branch by branching from a previous tag, then grabbing individual revisions from your main development branch. This command became popular among us Pidgin developers while we were preparing the Pidgin 2.5.6 release.

Here's the code:

--[[
Pluck a revision with the log and authors filled in for the next commit log.

@author: Sadrul Habib Chowdhury (updated for mtn 0.43 by John Bailey)
--]]

-- pluck-log command
function pluck_log(...)
local revs = {...}
local result
local topsrcdir
local logfile
local log

-- mtn_automate() returns a pair of a boolean and a string; we don't really
-- care about the boolean here, but we need to do something with it.
result, topsrcdir = mtn_automate("get_workspace_root")
topsrcdir = string.gsub(topsrcdir, "\n", "")
logfile = io.open(topsrcdir .. "/_MTN/log", "r")
log = ""

if logfile then
log = logfile:read("*all")
logfile:close()
end

table.foreach(revs,
function (index, rev)
r, sel = mtn_automate("select", rev)

if r == false then return end

for rev in sel:gmatch("%S+") do
r, certs = mtn_automate("certs", rev)

certs:gsub("%s+key \"(.-)\"\n%s*signature \"(.-)\"\n%s*name \"(.-)\"\n%s*value \"(.-)\"\n%s*trust \"(.-)\"",
function(key, sig, name, value, trust)
if name == "changelog" then
log = log .. "*** Plucked rev " .. rev .. " (" .. key .. "):\n" .. value .. "\n"
end
end
)
execute("mtn", "pluck", "-r", rev)
end
end
)

logfile = io.open(topsrcdir .. "/_MTN/log", "w")
logfile:write(log)
logfile:close()
end

register_command("pluck-log", "REVISION1 [REVISION2 [...]]", "Pluck a revision with a good log",
"This plucks a list of revisions, each individually, and adds the changelog of each revision for the next commit log." ..
"\nEXAMPLE:\tmtn pluck-log h:im.pidgin.pidgin deadbeef\n",
"pluck_log")


The resulting log entry will look something like this:

*** Plucked rev 074c5aedf9bbc512331f0d3130f076190b290676 (rekkanoryo@pidgin.im):
Set the default pager host to scsa.msg.yahoo.com; this seems to be what the
official client uses.


Originally, Sadrul wrote this code for mtn 0.42 and earlier, which did not have the mtn automate get_workspace_root functionality. I updated the code to call mtn_automate("get_workspace_root") instead of finding the workspace's root with successive checks of parent directories. The revision ID printed in the log was also truncated to the first 8 digits. Ordinarily, this is fine; however, it's possible that in the future a new revision will be created that has the same first 8 digits and thus the short ID will be ambiguous. I wanted to avoid this situation, so I removed the truncation. I also rearranged a few things to make it easier for me to read. I also have to credit my fellow Pidgin developer Elliott with figuring out that I needed to kill the trailing newline in the string returned from mtn_automate("get_workspace_root").

Hopefully we're not the only ones who find this stuff useful. As I make frequent use of other useful monotone lua hacks, I'll probably post about them here too.