Below is the file 'std_hooks.lua' from this revision. You can also download the file.
-- this is the standard set of lua hooks for monotone; -- user-provided files can override it or add to it. function temp_file() local tdir tdir = os.getenv("TMPDIR") if tdir == nil then tdir = os.getenv("TMP") end if tdir == nil then tdir = os.getenv("TEMP") end if tdir == nil then tdir = "/tmp" end return mkstemp(string.format("%s/mt.XXXXXX", tdir)) end function execute(path, ...) local pid local ret = -1 pid = spawn(path, unpack(arg)) if (pid ~= -1) then ret, pid = wait(pid) end return ret end -- Wrapper around execute to let user confirm in the case where a subprocess -- returns immediately -- This is needed to work around some brokenness with some merge tools -- (e.g. on OS X) function execute_confirm(path, ...) execute(path, unpack(arg)) print(gettext("Press enter when the subprocess has completed")) io.read() return ret end -- attributes are persistent metadata about files (such as execute -- bit, ACLs, various special flags) which we want to have set and -- re-set any time the files are modified. the attributes themselves -- are stored in a file .mt-attrs, in the working copy (and -- manifest). each (f,k,v) triple in an attribute file turns into a -- call to attr_functions[k](f,v) in lua. if (attr_init_functions == nil) then attr_init_functions = {} end attr_init_functions["execute"] = function(filename) if (is_executable(filename)) then return "true" else return nil end end attr_init_functions["manual_merge"] = function(filename) if (binary_file(filename)) then return "true" -- binary files must merged manually else return nil end end if (attr_functions == nil) then attr_functions = {} end attr_functions["execute"] = function(filename, value) if (value == "true") then make_executable(filename) end end function ignore_file(name) -- project specific if (ignored_files == nil) then ignored_files = {} local ignfile = io.open(".mt-ignore", "r") if (ignfile ~= nil) then local line = ignfile:read() while (line ~= nil) do table.insert(ignored_files, line) line = ignfile:read() end io.close(ignfile) end end for i, line in pairs(ignored_files) do if (regex.search(line, name)) then return true end end -- c/c++ 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, "^core$")) then return true end if (string.find(name, "/core$")) then return true end -- python if (string.find(name, "%.pyc$")) then return true end if (string.find(name, "%.pyo$")) then return true end -- TeX if (string.find(name, "%.aux$")) then return true end -- backup files 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 -- editor temp files -- vim creates .foo.swp files if (string.find(name, "%.[^/]*%.swp$")) then return true end -- emacs creates #foo# files if (string.find(name, "%#[^/]*%#$")) then return true end -- autotools detritus: if (string.find(name, "^autom4te.cache/")) then return true end if (string.find(name, "/autom4te.cache/")) then return true end if (string.find(name, "^.deps/")) then return true end if (string.find(name, "/.deps/")) then return true end -- Cons/SCons detritus: if (string.find(name, "^.consign$")) then return true end if (string.find(name, "/.consign$")) then return true end if (string.find(name, "^.sconsign$")) then return true end if (string.find(name, "/.sconsign$")) then return true end -- other VCSes: 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 if (string.find(name, "^_darcs/")) then return true end if (string.find(name, "^.cdv/")) then return true end if (string.find(name, "^.git/")) then return true end if (string.find(name, "%.scc$")) then return true end -- desktop/directory configuration metadata if (string.find(name, "^.DS_Store$")) then return true end if (string.find(name, "/.DS_Store$")) then return true end if (string.find(name, "^desktop.ini$")) then return true end if (string.find(name, "/desktop.ini$")) then return true end return false; end -- return true means "binary", false means "text", -- nil means "unknown, try to guess" function binary_file(name) local lowname=string.lower(name) -- some known binaries, return true if (string.find(lowname, "%.gif$")) then return true end if (string.find(lowname, "%.jpe?g$")) then return true end if (string.find(lowname, "%.png$")) then return true end if (string.find(lowname, "%.bz2$")) then return true end if (string.find(lowname, "%.gz$")) then return true end if (string.find(lowname, "%.zip$")) then return true end -- some known text, return false if (string.find(lowname, "%.cc?$")) then return false end if (string.find(lowname, "%.cxx$")) then return false end if (string.find(lowname, "%.hh?$")) then return false end if (string.find(lowname, "%.hxx$")) then return false end if (string.find(lowname, "%.lua$")) then return false end if (string.find(lowname, "%.texi$")) then return false end if (string.find(lowname, "%.sql$")) then return false end -- unknown - read file and use the guess-binary -- monotone built-in function return guess_binary_file_contents(name) end function edit_comment(basetext, user_log_message) local exe = nil if (program_exists_in_path("vi")) then exe = "vi" end if (program_exists_in_path("notepad.exe")) then exe = "notepad.exe" end local visual = os.getenv("VISUAL") if (visual ~= nil) then exe = visual end local editor = os.getenv("EDITOR") if (editor ~= nil) then exe = editor end if (exe == nil) then io.write("Could not find editor to enter commit message\n" .. "Try setting the environment variable EDITOR\n") return nil end local tmp, tname = temp_file() if (tmp == nil) then return nil end basetext = "MT: " .. string.gsub(basetext, "\n", "\nMT: ") .. "\n" tmp:write(user_log_message) tmp:write(basetext) io.close(tmp) if (execute(exe, tname) ~= 0) then io.write(string.format(gettext("Error running editor '%s' to enter log message\n"), exe)) 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 function non_blocking_rng_ok() return true end function persist_phrase_ok() return true end -- trust evaluation hooks 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) return true end function get_manifest_cert_trust(signers, id, name, val) return true end function get_file_cert_trust(signers, id, name, val) return true end function accept_testresult_change(old_results, new_results) local reqfile = io.open("MT/wanted-testresults", "r") if (reqfile == nil) then return true end local line = reqfile:read() local required = {} while (line ~= nil) do required[line] = true line = reqfile:read() end io.close(reqfile) for test, res in pairs(required) do if old_results[test] == true and new_results[test] ~= true then return false end end return true end -- merger support function merge2_meld_cmd(lfile, rfile) return function() return execute("meld", lfile, rfile) end end function merge3_meld_cmd(lfile, afile, rfile) return function() return execute("meld", lfile, afile, rfile) end end function merge2_tortoise_cmd(lfile, rfile, outfile) return function() return execute("tortoisemerge", string.format("/theirs:%s", lfile), string.format("/mine:%s", rfile), string.format("/merged:%s", outfile)) end end function merge3_tortoise_cmd(lfile, afile, rfile, outfile) return function() return execute("tortoisemerge", string.format("/base:%s", afile), string.format("/theirs:%s", lfile), string.format("/mine:%s", rfile), string.format("/merged:%s", outfile)) end end function merge2_vim_cmd(vim, lfile, rfile, outfile) return function() return execute(vim, "-f", "-d", "-c", string.format("file %s", outfile), lfile, rfile) end end function merge3_vim_cmd(vim, afile, lfile, rfile, outfile) return function() return execute(vim, "-f", "-d", "-c", string.format("file %s", outfile), afile, lfile, rfile) end end function merge3_rcsmerge_vim_cmd(merge, vim, lfile, afile, rfile, outfile) return function() -- XXX: This is tough - should we check if conflict markers stay or not? -- If so, we should certainly give the user some way to still force -- the merge to proceed since they can appear in the files (and I saw -- that). --pasky if execute(merge, lfile, afile, rfile) == 0 then copy_text_file(lfile, outfile); return 0 end return execute(vim, "-f", "-c", string.format("file %s", outfile), lfile) end end function merge2_emacs_cmd(emacs, lfile, rfile, outfile) local elisp = "(ediff-merge-files \"%s\" \"%s\" nil \"%s\")" return function() return execute(emacs, "-eval", string.format(elisp, lfile, rfile, outfile)) end end function merge3_emacs_cmd(emacs, lfile, afile, rfile, outfile) local elisp = "(ediff-merge-files-with-ancestor \"%s\" \"%s\" \"%s\" nil \"%s\")" local cmd_fmt = "%s -eval " .. elisp return function() execute(emacs, "-eval", string.format(elisp, lfile, rfile, afile, outfile)) end end function merge2_xxdiff_cmd(left_path, right_path, merged_path, lfile, rfile, outfile) return function() return execute("xxdiff", "--title1", left_path, "--title2", right_path, lfile, rfile, "--merged-filename", outfile) end end function merge3_xxdiff_cmd(left_path, anc_path, right_path, merged_path, lfile, afile, rfile, outfile) return function() return execute("xxdiff", "--title1", left_path, "--title2", right_path, "--title3", merged_path, lfile, afile, rfile, "--merge", "--merged-filename", outfile) end end function merge2_kdiff3_cmd(left_path, right_path, merged_path, lfile, rfile, outfile) return function() return execute("kdiff3", "--L1", left_path, "--L2", right_path, lfile, rfile, "-o", outfile) end end function merge3_kdiff3_cmd(left_path, anc_path, right_path, merged_path, lfile, afile, rfile, outfile) return function() return execute("kdiff3", "--L1", anc_path, "--L2", left_path, "--L3", right_path, afile, lfile, rfile, "--merge", "--o", outfile) end end function merge2_opendiff_cmd(left_path, right_path, merged_path, lfile, rfile, outfile) return function() -- As opendiff immediately returns, let user confirm manually return execute_confirm("opendiff",lfile,rfile,"-merge",outfile) end end function merge3_opendiff_cmd(left_path, anc_path, right_path, merged_path, lfile, afile, rfile, outfile) return function() -- As opendiff immediately returns, let user confirm manually execute_confirm("opendiff",lfile,rfile,"-ancestor",afile,"-merge",outfile) end end function write_to_temporary_file(data) tmp, filename = temp_file() if (tmp == nil) then return nil end; tmp:write(data) io.close(tmp) return filename end function copy_text_file(srcname, destname) src = io.open(srcname, "r") if (src == nil) then return nil end dest = io.open(destname, "w") if (dest == nil) then return nil end while true do local line = src:read() if line == nil then break end dest:write(line, "\n") end io.close(dest) io.close(src) end function read_contents_of_file(filename, mode) tmp = io.open(filename, mode) if (tmp == nil) then return nil end local data = tmp:read("*a") io.close(tmp) return data end function program_exists_in_path(program) return existsonpath(program) == 0 end function get_preferred_merge2_command (tbl) local cmd = nil local left_path = tbl.left_path local right_path = tbl.right_path local merged_path = tbl.merged_path local lfile = tbl.lfile local rfile = tbl.rfile local outfile = tbl.outfile local editor = os.getenv("EDITOR") if editor ~= nil then editor = string.lower(editor) else editor = "" end if program_exists_in_path("kdiff3") then cmd = merge2_kdiff3_cmd (left_path, right_path, merged_path, lfile, rfile, outfile) elseif program_exists_in_path ("xxdiff") then cmd = merge2_xxdiff_cmd (left_path, right_path, merged_path, lfile, rfile, outfile) elseif program_exists_in_path ("opendiff") then cmd = merge2_opendiff_cmd (left_path, right_path, merged_path, lfile, rfile, outfile) elseif program_exists_in_path ("TortoiseMerge") then cmd = merge2_tortoise_cmd(lfile, rfile, outfile) elseif string.find(editor, "emacs") ~= nil or string.find(editor, "gnu") ~= nil then if string.find(editor, "xemacs") and program_exists_in_path("xemacs") then cmd = merge2_emacs_cmd ("xemacs", lfile, rfile, outfile) elseif program_exists_in_path("emacs") then cmd = merge2_emacs_cmd ("emacs", lfile, rfile, outfile) end elseif string.find(editor, "vim") ~= nil then io.write (string.format("\nWARNING: 'vim' was choosen to perform external 2-way merge.\n".. "You should merge all changes to *LEFT* file due to limitation of program\n".. "arguments.\n\n")) if os.getenv ("DISPLAY") ~= nil and program_exists_in_path ("gvim") then cmd = merge2_vim_cmd ("gvim", lfile, rfile, outfile) elseif program_exists_in_path ("vim") then cmd = merge2_vim_cmd ("vim", lfile, rfile, outfile) end elseif program_exists_in_path ("meld") then tbl.meld_exists = true io.write (string.format("\nWARNING: 'meld' was choosen to perform external 2-way merge.\n".. "You should merge all changes to *LEFT* file due to limitation of program\n".. "arguments.\n\n")) cmd = merge2_meld_cmd (lfile, rfile) end return cmd end function merge2 (left_path, right_path, merged_path, left, right) local ret = nil local tbl = {} tbl.lfile = nil tbl.rfile = nil tbl.outfile = nil tbl.meld_exists = false tbl.lfile = write_to_temporary_file (left) tbl.rfile = write_to_temporary_file (right) tbl.outfile = write_to_temporary_file ("") if tbl.lfile ~= nil and tbl.rfile ~= nil and tbl.outfile ~= nil then tbl.left_path = left_path tbl.right_path = right_path tbl.merged_path = merged_path local cmd = get_preferred_merge2_command (tbl) if cmd ~=nil then io.write (string.format(gettext("executing external 2-way merge command\n"))) cmd () if tbl.meld_exists then ret = read_contents_of_file (tbl.lfile, "r") else ret = read_contents_of_file (tbl.outfile, "r") end if string.len (ret) == 0 then ret = nil end else io.write (string.format("No external 2-way merge command found.\n".. "You may want to check that $EDITOR is set to an editor that supports 2-way merge,\n".. "set this explicitly in your get_preferred_merge2_command hook,\n".. "or add a 2-way merge program to your path.\n\n")) end end os.remove (tbl.lfile) os.remove (tbl.rfile) os.remove (tbl.outfile) return ret end function get_preferred_merge3_command (tbl) local cmd = nil local left_path = tbl.left_path local anc_path = tbl.anc_path local right_path = tbl.right_path local merged_path = tbl.merged_path local lfile = tbl.lfile local afile = tbl.afile local rfile = tbl.rfile local outfile = tbl.outfile local editor = os.getenv("EDITOR") if editor ~= nil then editor = string.lower(editor) else editor = "" end local merge = os.getenv("MTMERGE") -- TODO: Support for rcsmerge_emacs if merge ~= nil and string.find(editor, "vim") ~= nil then if os.getenv ("DISPLAY") ~= nil and program_exists_in_path ("gvim") then cmd = merge3_rcsmerge_vim_cmd (merge, "gvim", lfile, afile, rfile, outfile) elseif program_exists_in_path ("vim") then cmd = merge3_rcsmerge_vim_cmd (merge, "vim", lfile, afile, rfile, outfile) end elseif program_exists_in_path("kdiff3") then cmd = merge3_kdiff3_cmd (left_path, anc_path, right_path, merged_path, lfile, afile, rfile, outfile) elseif program_exists_in_path ("xxdiff") then cmd = merge3_xxdiff_cmd (left_path, anc_path, right_path, merged_path, lfile, afile, rfile, outfile) elseif program_exists_in_path ("opendiff") then cmd = merge3_opendiff_cmd (left_path, anc_path, right_path, merged_path, lfile, afile, rfile, outfile) elseif program_exists_in_path ("TortoiseMerge") then cmd = merge3_tortoise_cmd(lfile, afile, rfile, outfile) elseif string.find(editor, "emacs") ~= nil or string.find(editor, "gnu") ~= nil then if string.find(editor, "xemacs") and program_exists_in_path ("xemacs") then cmd = merge3_emacs_cmd ("xemacs", lfile, afile, rfile, outfile) elseif program_exists_in_path ("emacs") then cmd = merge3_emacs_cmd ("emacs", lfile, afile, rfile, outfile) end elseif string.find(editor, "vim") ~= nil then io.write (string.format("\nWARNING: 'vim' was choosen to perform external 2-way merge.\n".. "You should merge all changes to *LEFT* file due to limitation of program\n".. "arguments. The order of the files is ancestor, left, right.\n\n")) if os.getenv ("DISPLAY") ~= nil and program_exists_in_path ("gvim") then cmd = merge3_vim_cmd ("gvim", afile, lfile, rfile, outfile) elseif program_exists_in_path ("vim") then cmd = merge3_vim_cmd ("vim", afile, lfile, rfile, outfile) end elseif program_exists_in_path ("meld") then tbl.meld_exists = true io.write (string.format("\nWARNING: 'meld' was choosen to perform external 3-way merge.\n".. "You should merge all changes to *CENTER* file due to limitation of program\n".. "arguments.\n\n")) cmd = merge3_meld_cmd (lfile, afile, rfile) end return cmd end function merge3 (anc_path, left_path, right_path, merged_path, ancestor, left, right) local ret local tbl = {} tbl.anc_path = anc_path tbl.left_path = left_path tbl.right_path = right_path tbl.merged_path = merged_path tbl.afile = nil tbl.lfile = nil tbl.rfile = nil tbl.outfile = nil tbl.meld_exists = false tbl.lfile = write_to_temporary_file (left) tbl.afile = write_to_temporary_file (ancestor) tbl.rfile = write_to_temporary_file (right) tbl.outfile = write_to_temporary_file ("") if tbl.lfile ~= nil and tbl.rfile ~= nil and tbl.afile ~= nil and tbl.outfile ~= nil then local cmd = get_preferred_merge3_command (tbl) if cmd ~=nil then io.write (string.format(gettext("executing external 3-way merge command\n"))) cmd () if tbl.meld_exists then ret = read_contents_of_file (tbl.afile, "r") else ret = read_contents_of_file (tbl.outfile, "r") end if string.len (ret) == 0 then ret = nil end else io.write (string.format("No external 3-way merge command found.\n".. "You may want to check that $EDITOR is set to an editor that supports 3-way merge,\n".. "set this explicitly in your get_preferred_merge3_command hook,\n".. "or add a 3-way merge program to your path.\n\n")) end end os.remove (tbl.lfile) os.remove (tbl.rfile) os.remove (tbl.afile) os.remove (tbl.outfile) return ret end -- expansion of values used in selector completion function expand_selector(str) -- something which looks like a generic cert pattern if string.find(str, "^[^=]*=.*$") then return ("c:" .. 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 -- tries to expand as a date local dtstr = expand_date(str) if dtstr ~= nil then return ("d:" .. dtstr) end return nil end -- expansion of a date expression function expand_date(str) -- simple date patterns if string.find(str, "^19%d%d%-%d%d") or string.find(str, "^20%d%d%-%d%d") then return (str) end -- "now" if str == "now" then local t = os.time(os.date('!*t')) return os.date("%FT%T", t) end -- today don't uses the time if str == "today" then local t = os.time(os.date('!*t')) return os.date("%F", t) end -- "yesterday", the source of all hangovers if str == "yesterday" then local t = os.time(os.date('!*t')) return os.date("%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')) if trans[type] <= 3600 then return os.date("%FT%T", t - (n * trans[type])) else return os.date("%F", t - (n * trans[type])) end end return nil end function use_inodeprints() return false end external_diff_default_args = "-u" -- default external diff, works for gnu diff function external_diff(file_path, data_old, data_new, is_binary, diff_args, rev_old, rev_new) local old_file = write_to_temporary_file(data_old); local new_file = write_to_temporary_file(data_new); if diff_args == nil then diff_args = external_diff_default_args end execute("diff", diff_args, "--label", file_path .. "\told", old_file, "--label", file_path .. "\tnew", new_file); os.remove (old_file); os.remove (new_file); end