Monotone's behavior can be customized and extended by writing hook functions, which are written in the Lua programming language. At certain points in time, when monotone is running, it will call a hook function to help it make a decision or perform some action. If you provide a hook function definition which suits your preferences, monotone will execute it. This way you can modify how monotone behaves.
You can put new definitions for any of these hook functions in a file $HOME/.monotonerc, or in your working copy in MT/monotonerc, both of which will be read every time monotone runs. Definitions in MT/monotonerc shadow (override) definitions made in your $HOME/.monotonerc. You can also tell monotone to interpret extra hook functions from any other file using the --rcfile=file option; hooks defined in files specified on the command-line will shadow hooks from the the automatic files.
The remainder of this section documents the existing hook functions and their default definitions.
note_commit (
new_id,
certs)
Note that since the certs table does not contain cryptographic
or trust information, and only contains one entry per cert name, it is
an incomplete source of information about the committed version. This
hook is only intended as an aid for integrating monotone with informal
commit-notification systems such as mailing lists or news services. It
should not perform any security-critical operations.
get_branch_key (
branchname)
get_passphrase (
keypair_id)
"nicole@example.com"
. This hook has no default definition. If
this hook is not defined or returns false, monotone will prompt you for
a passphrase each time it needs to use a private key.
get_author (
branchname)
author
certificates when you commit changes to
branchname. Generally this hook remains undefined, and monotone
selects your signing key name for the author certificate. You can use
this hook to override that choice, if you like.
This hook has no default definition, but a possible definition might be:
function get_author(branchname) local user = os.getenv("USER") local host = os.getenv("HOSTNAME") if ((user == nil) or (host == nil)) then return nil end return string.format("%s@%s", user, host) end
edit_comment (
commentary)
changelog
certificate, automatically generated when you commit
changes.
The default definition of this hook is:
function edit_comment(commentary) local exe = "vi" local visual = os.getenv("VISUAL") if (visual ~= nil) then exe = visual end local editor = os.getenv("EDITOR") if (editor ~= nil) then exe = editor end local tmp, tname = temp_file() if (tmp == nil) then return nil end commentary = "MT: " .. string.gsub(commentary, "\n", "\nMT: ") tmp:write(commentary) io.close(tmp) if (os.execute(string.format("%s %s", exe, tname)) ~= 0) then os.remove(tname) return nil end tmp = io.open(tname, "r") if (tmp == nil) then os.remove(tname); return nil end local res = "" local line = tmp:read() while(line ~= nil) do if (not string.find(line, "^MT:")) then res = res .. line .. "\n" end line = tmp:read() end io.close(tmp) os.remove(tname) return res end
persist_phrase_ok ()
true
if you want monotone to remember the passphrase of
a private key for the duration of a single command, or false
if
you want monotone to prompt you for a passphrase for each certificate
it generates. Since monotone often generates several certificates in
quick succession, unless you are very concerned about security you
probably want this hook to return true
.
The default definition of this hook is:
function persist_phrase_ok() return true end
non_blocking_rng_ok ()
true
if you are willing to let monotone use the
system's non-blocking random number generator, such as
/dev/urandom, for generating random values during cryptographic
operations. This diminishes the cryptographic strength of such
operations, but speeds them up. Returns false
if you want to
force monotone to always use higher quality random numbers, such as
those from /dev/random.
The default definition of this hook is:
function non_blocking_rng_ok() return true end
get_netsync_read_permitted (
collection,
identity)
true
if a peer authenticated as key identity
should be allowed to read from your database certs, revisions,
manifests, and files associated with the netsync index
collection; otherwise false
. This hook has no default
definition, therefore the default behavior is to deny all reads.
Note that the identity value is a key id (such as
“graydon@pobox.com
”) but will correspond to a unique
key fingerprint (hash) in your database. Monotone will not permit two
keys in your database to have the same id. Make sure you confirm the
key fingerprints of each key in your database, as key id strings are
“convenience names”, not security tokens.
get_netsync_anonymous_read_permitted (
collection)
get_netsync_read_permitted
except that it is called when a connecting client requests anonymous
read access to a collection. There is no corresponding anonymous write
access hook. This hook has no default definition, therefore the
default behavior is to deny all anonymous reads.
get_netsync_write_permitted (
collection,
identity)
true
if a peer authenticated as key identity
should be allowed to write into your database certs, revisions,
manifests, and files associated with the netsync index
collection; otherwise false
. This hook has no default
definition, therefore the default behavior is to deny all writes.
Note that the identity value is a key id (such as
“graydon@pobox.com
”) but will correspond to a unique
key fingerprint (hash) in your database. Monotone will not permit two
keys in your database to have the same id. Make sure you confirm the
key fingerprints of each key in your database, as key id strings are
“convenience names”, not security tokens.
ignore_file (
filename)
true
if filename should be ignored while adding,
dropping, or moving files. Otherwise returns false
. This is
most important when performing recursive actions on directories, which
may affect multiple files simultaneously. The default definition of
this hook is:
function ignore_file(name) if (string.find(name, "%.a$")) then return true end if (string.find(name, "%.so$")) then return true end if (string.find(name, "%.o$")) then return true end if (string.find(name, "%.la$")) then return true end if (string.find(name, "%.lo$")) then return true end if (string.find(name, "%.aux$")) then return true end if (string.find(name, "%.bak$")) then return true end if (string.find(name, "%.orig$")) then return true end if (string.find(name, "%.rej$")) then return true end if (string.find(name, "%~$")) then return true end if (string.find(name, "/core$")) then return true end if (string.find(name, "^CVS/")) then return true end if (string.find(name, "/CVS/")) then return true end if (string.find(name, "^%.svn/")) then return true end if (string.find(name, "/%.svn/")) then return true end if (string.find(name, "^SCCS/")) then return true end if (string.find(name, "/SCCS/")) then return true end return false; end
ignore_branch (
branchname)
true
if branchname should be ignored while listing
branches. Otherwise returns false
. This hook has no default
definition, therefore the default behavior is to list all branches.
get_revision_cert_trust (
signers,
id,
name,
val)
The default definition of this hook simply returns true
, which
corresponds to a form of trust where every key which is defined in
your database is trusted. This is a weak trust setting; you
should change it to something stronger. A possible example of a
stronger trust function (along with a utility function for computing
the intersection of tables) is the following:
function intersection(a,b) local s={} local t={} for k,v in pairs(a) do s[v] = 1 end for k,v in pairs(b) do if s[v] ~= nil then table.insert(t,v) end end return t end function get_revision_cert_trust(signers, id, name, val) local trusted_signers = { "bob@happyplace.example.com", "friend@trustedplace.example.com", "myself@home.example.com" } local t = intersection(signers, trusted_signers) if t == nil then return false end if (name ~= "ancestor" and table.getn(t) >= 1) or (name == "ancestor" and table.getn(t) >= 2) then return true else return false end end
In this example, any revision certificate is trusted if it is signed
by at least one of three “trusted” keys, unless it is an
ancestor
certificate, in which case it must be signed by
two or more trusted keys. This is one way of requiring that
ancestry assertions go through an extra “reviewer” before they are
accepted.
accept_testresult_change (
old_results,
new_results)
true
if you consider an update from the
version carrying the old_results to the version carrying the
new_results to be acceptable.
The default definition of this hook follows:
function accept_testresult_change(old_results, new_results) for test,res in pairs(old_results) do if res == true and new_results[test] ~= true then return false end end return true end
This definition accepts only those updates which preserve the set of
true
test results from update source to target. If no rest
results exist, this hook has no affect; but once a true
test
result is present, future updates will require it. If you want a more
lenient behavior you must redefine this hook.
merge2 (
left,
right)
function merge2(left, right) local lfile = nil local rfile = nil local outfile = nil local data = nil lfile = write_to_temporary_file(left) rfile = write_to_temporary_file(right) outfile = write_to_temporary_file("") if lfile ~= nil and rfile ~= nil and outfile ~= nil then local cmd = nil if program_exists_in_path("xxdiff") then cmd = merge2_xxdiff_cmd(lfile, rfile, outfile) elseif program_exists_in_path("emacs") then cmd = merge2_emacs_cmd("emacs", lfile, rfile, outfile) elseif program_exists_in_path("xemacs") then cmd = merge2_emacs_cmd("xemacs", lfile, rfile, outfile) end if cmd ~= nil then io.write( string.format("executing external 2-way merge command: %s\n", cmd)) os.execute(cmd) data = read_contents_of_file(outfile) else io.write("no external 2-way merge command found") end end os.remove(lfile) os.remove(rfile) os.remove(outfile) return data end
merge3 (
ancestor,
left,
right)
function merge3(ancestor, left, right) local afile = nil local lfile = nil local rfile = nil local outfile = nil local data = nil lfile = write_to_temporary_file(left) afile = write_to_temporary_file(ancestor) rfile = write_to_temporary_file(right) outfile = write_to_temporary_file("") if lfile ~= nil and rfile ~= nil and afile ~= nil and outfile ~= nil then local cmd = nil if program_exists_in_path("xxdiff") then cmd = merge3_xxdiff_cmd(lfile, afile, rfile, outfile) elseif program_exists_in_path("emacs") then cmd = merge3_emacs_cmd("emacs", lfile, afile, rfile, outfile) elseif program_exists_in_path("xemacs") then cmd = merge3_emacs_cmd("xemacs", lfile, afile, rfile, outfile) end if cmd ~= nil then io.write( string.format("executing external 3-way merge command: %s\n", cmd)) os.execute(cmd) data = read_contents_of_file(outfile) else io.write("no external 3-way merge command found") end end os.remove(lfile) os.remove(rfile) os.remove(afile) os.remove(outfile) return data end
expand_selector (
str)
a:
for
authors or d:
for dates. Expansion may also mean recognizing
and interpreting special words such as yesterday
or 6
months ago
and converting them into well formed selectors. For more
detail on the use of selectors, see Selectors. The default
definition of this hook is:
function expand_selector(str) -- simple date patterns if string.find(str, "^19%d%d%-%d%d") or string.find(str, "^20%d%d%-%d%d") then return ("d:" .. str) end -- something which looks like an email address if string.find(str, "[%w%-_]+@[%w%-_]+") then return ("a:" .. str) end -- something which looks like a branch name if string.find(str, "[%w%-]+%.[%w%-]+") then return ("b:" .. str) end -- a sequence of nothing but hex digits if string.find(str, "^%x+$") then return ("i:" .. str) end -- "yesterday", the source of all hangovers if str == "yesterday" then local t = os.time(os.date('!*t')) return os.date("d:%F", t - 86400) end -- "CVS style" relative dates such as "3 weeks ago" local trans = { minute = 60; hour = 3600; day = 86400; week = 604800; month = 2678400; year = 31536000 } local pos, len, n, type = string.find(str, "(%d+) ([minutehordaywk]+)s? ago") if trans[type] ~= nil then local t = os.time(os.date('!*t')) return os.date("d:%F", t - (n * trans[type])) end return nil end
get_system_linesep ()
CR
, LF
, or
CRLF
. The system line separator may be used when reading or
writing data to the terminal, or otherwise interfacing with the user.
The system line separator is not used to convert files in the working
copy; use get_linesep_conv
for converting line endings in the
working copy.
This hook has no default definition. For more information on line
ending conversion, see the section on Internationalization.
get_linesep_conv (
filename)
CR
, LF
, or
CRLF
.
When filename is read from the working copy, it is run through line ending conversion from the external form to the internal form. When filename is written to the working copy, it is run through line ending conversion from the internal form to the external form. sha1 values are calculated from the internal form of filename. It is your responsibility to decide which line ending conversions your work will use.
This hook has no default definition; monotone's default behavior is to
keep external and internal forms byte-for-byte identical. For more
information on line ending conversion, see the section on
Internationalization.
get_charset_conv (
filename)
When filename is read from the working copy, it is run through character set conversion from the external form to the internal form. When filename is written to the working copy, it is run through character set conversion from the internal form to the external form. sha1 values are calculated from the internal form of filename. It is your responsibility to decide which character set conversions your work will use.
This hook has no default definition; monotone's default behavior is to
keep external and internal forms byte-for-byte identical. For more
information on character set conversion, see the section on
Internationalization.
attr_functions [
attribute] (
filename,
value)
attr_functions
, at table
entry attribute, is a function taking a file name filename
and a attribute value value. The function should “apply” the
attribute to the file, possibly in a platform-specific way.
Persistent attributes are stored in the .mt-attrs, in your working copy and manifest. If such a file exists, hook functions from this table are called for each triple found in the file, after any command which modifies the working copy. This facility can be used to extend monotone's understanding of files with platform-specific attributes, such as permission bits, access control lists, or special file types.
By default, there is only one entry in this table, for the execute
attribute. Its definition is:
attr_functions["execute"] = function(filename, value) if (value == "true") then os.execute(string.format("chmod +x %s", filename)) end end