Next: , Previous: Command Reference, Up: Top



6 Hook Reference

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)
Called by monotone after the version new_id is committed. The second parameter, certs, is a lua table containing the set of certificate names and values committed along with this version. There is no default definition for this hook.

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)
Returns a string which is the name of an rsa private key used to sign certificates in a particular branch branchname. There is no default definition for this hook. The command-line option --key=keyname overrides any value returned from this hook function. If you have only one private key in your database, you do not need to define this function or provide a --key=keyname option; monotone will guess that you want to use the unique private key.
get_passphrase (keypair_id)
Returns a string which is the passphrase used to encrypt the private half of keypair_id in your database, using the arc4 symmetric cipher. keypair_id is a Lua string containing the label that you used when you created your key — something like "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)
Returns a string which is used as a value for automatically generated 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)
Returns a log entry for a given set of changes, described in commentary. The commentary is identical to the output of monotone status. This hook is intended to interface with some sort of editor, so that you can interactively document each change you make. The result is used as the value for a 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 ()
Returns 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 ()
Returns 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)
Returns 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)
This hook has identical semantics to 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)
Returns 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)
Returns 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)
Returns 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)
Returns whether or not you trust the assertion name=value on a given revision id, given a valid signature from all the keys in signers. The signers parameter is a table containing all the key names which signed this cert, the other three parameters are strings.

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)
This hook is used by the update algorithm to determine whether a change in test results between update source and update target is acceptable. The hook is called with two tables, each of which maps a signing key – representing a particular testsuite – to a boolean value indicating whether or not the test run was successful. The function should return 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)
Returns a string, which should be the merger of the 2 provided strings, which are the contents of the left and right nodes of a file fork which monotone was unable to automatically merge. The merge should either call an intelligent merge program or interact with the user. The default definition of this hook is:
          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)
Returns a string, which should be the merger of the 3 provided strings, which are the contents of left and right nodes, and least common ancestor, of a file fork which monotone was unable to automatically merge. The merge should either call an intelligent merge program or interact with the user. The default definition of this hook is:
          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)
Attempts to expand str as a selector. Expansion generally means providing a type prefix for the selector, such as 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 ()
Returns a string which defines the default system line separator. This should be one of the strings 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)
Returns a table which contains two strings. The first string in the return value is the name of a line ending convention to use for the “internal” representation of filename. The second string in the return value is the name of a line ending convention to use for the “external” representation of filename. Line ending conventions should be one of the strings 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)
Returns a table which contains two strings. The first string in the return value is the name of a character set to use for the “internal” representation of filename. The second string in the return value is the name of a character set to use for the “external” representation of 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)
This is not a hook function, but a table of hook functions. Each entry in the table 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