Below is the file 'lua.cc' from this revision. You can also download the file.
// -*- mode: C++; c-file-style: "gnu"; indent-tabs-mode: nil -*- // vim: et:sw=2:sts=2:ts=2:cino=>2s,{s,\:s,+s,t0,g0,^-2,e-2,n-2,p2s,(0,=s: // copyright (C) 2002, 2003 graydon hoare <graydon@pobox.com> // all rights reserved. // licensed to the public under the terms of the GNU GPL (>= 2) // see the file COPYING for details #include "config.h" extern "C" { #include <lua.h> #include <lualib.h> #include <lauxlib.h> } #include <errno.h> #include <stdlib.h> #include <string.h> #include <stdarg.h> #include <signal.h> #include <boost/lexical_cast.hpp> #include <boost/filesystem/path.hpp> #include <boost/filesystem/operations.hpp> #include <boost/regex.hpp> #include <set> #include <map> #include <fstream> #include "app_state.hh" #include "file_io.hh" #include "lua.hh" #include "mkstemp.hh" #include "sanity.hh" #include "vocab.hh" #include "platform.hh" #include "transforms.hh" #include "paths.hh" #include "globish.hh" #include "basic_io.hh" // defined in {std,test}_hooks.lua, converted #include "test_hooks.h" #include "std_hooks.h" using namespace std; using boost::lexical_cast; // this lets the lua callbacks (monotone_*_for_lua) have access to the // app_state the're associated with. // it was added so that the confdir (normally ~/.monotone) can be specified on // the command line (and so known only to the app_state), and still be // available to lua // please *don't* use it for complex things that can throw errors static std::map<lua_State*, app_state*> map_of_lua_to_app; static int panic_thrower(lua_State * st) { throw oops("lua panic"); } // adapted from "programming in lua", section 24.2.3 // http://www.lua.org/pil/24.2.3.html // output is from bottom (least accessible) to top (most accessible, where // push and pop happen). static std::string dump_stack(lua_State * st) { std::string out; int i; int top = lua_gettop(st); for (i = 1; i <= top; i++) { /* repeat for each level */ int t = lua_type(st, i); switch (t) { case LUA_TSTRING: /* strings */ out += (boost::format("`%s'") % std::string(lua_tostring(st, i), lua_strlen(st, i))).str(); break; case LUA_TBOOLEAN: /* booleans */ out += (lua_toboolean(st, i) ? "true" : "false"); break; case LUA_TNUMBER: /* numbers */ out += (boost::format("%g") % lua_tonumber(st, i)).str(); break; default: /* other values */ out += (boost::format("%s") % lua_typename(st, t)).str(); break; } out += " "; /* put a separator */ } return out; } // This Lua object represents a single imperative transaction with the lua // interpreter. if it fails at any point, all further commands in the // transaction are ignored. it cleans the lua stack up when it is // destructed, so no need to pop values when you're done. struct Lua { lua_State * st; bool failed; static std::set<string> missing_functions; Lua(lua_State * s) : st(s), failed(false) {} ~Lua() { lua_settop(st, 0); } void fail(std::string const & reason) { L(FL("lua failure: %s; stack = %s\n") % reason % dump_stack(st)); failed = true; } bool ok() { if (failed) L(FL("Lua::ok(): failed")); return !failed; } void report_error() { I(lua_isstring(st, -1)); string err = string(lua_tostring(st, -1), lua_strlen(st, -1)); W(i18n_format("%s\n") % err); L(FL("lua stack: %s") % dump_stack(st)); lua_pop(st, 1); failed = true; } // getters Lua & get(int idx = LUA_GLOBALSINDEX) { if (failed) return *this; if (!lua_istable (st, idx)) { fail("istable() in get"); return *this; } if (lua_gettop (st) < 1) { fail("stack top > 0 in get"); return *this; } lua_gettable(st, idx); return *this; } Lua & get_fn(int idx = LUA_GLOBALSINDEX) { if (failed) return *this; get(idx); if (!lua_isfunction (st, -1)) fail("isfunction() in get_fn"); return *this; } Lua & get_tab(int idx = LUA_GLOBALSINDEX) { if (failed) return *this; get(idx); if (!lua_istable (st, -1)) fail("istable() in get_tab"); return *this; } Lua & get_str(int idx = LUA_GLOBALSINDEX) { if (failed) return *this; get(idx); if (!lua_isstring (st, -1)) fail("isstring() in get_str"); return *this; } Lua & get_num(int idx = LUA_GLOBALSINDEX) { if (failed) return *this; get(idx); if (!lua_isnumber (st, -1)) fail("isnumber() in get_num"); return *this; } Lua & get_bool(int idx = LUA_GLOBALSINDEX) { if (failed) return *this; get(idx); if (!lua_isboolean (st, -1)) fail("isboolean() in get_bool"); return *this; } // extractors Lua & extract_str(string & str) { if (failed) return *this; if (!lua_isstring (st, -1)) { fail("isstring() in extract_str"); return *this; } str = string(lua_tostring(st, -1), lua_strlen(st, -1)); L(FL("lua: extracted string = %s") % str); return *this; } Lua & extract_int(int & i) { if (failed) return *this; if (!lua_isnumber (st, -1)) { fail("isnumber() in extract_int"); return *this; } i = static_cast<int>(lua_tonumber(st, -1)); L(FL("lua: extracted int = %i") % i); return *this; } Lua & extract_double(double & i) { if (failed) return *this; if (!lua_isnumber (st, -1)) { fail("isnumber() in extract_double"); return *this; } i = lua_tonumber(st, -1); L(FL("lua: extracted double = %i") % i); return *this; } Lua & extract_bool(bool & i) { if (failed) return *this; if (!lua_isboolean (st, -1)) { fail("isboolean() in extract_bool"); return *this; } i = (lua_toboolean(st, -1) == 1); L(FL("lua: extracted bool = %i") % i); return *this; } // table iteration Lua & begin() { if (failed) return *this; if (!lua_istable(st, -1)) { fail("istable() in begin"); return *this; } I(lua_checkstack (st, 1)); lua_pushnil(st); return *this; } bool next() { if (failed) return false; if (!lua_istable(st, -2)) { fail("istable() in next"); return false; } I(lua_checkstack (st, 1)); if (lua_next(st, -2) != 0) { return true; } pop(); return false; } // pushers Lua & push_str(string const & str) { if (failed) return *this; I(lua_checkstack (st, 1)); lua_pushlstring(st, str.c_str(), str.size()); return *this; } Lua & push_int(int num) { if (failed) return *this; I(lua_checkstack (st, 1)); lua_pushnumber(st, num); return *this; } Lua & push_int(double num) { if (failed) return *this; I(lua_checkstack (st, 1)); lua_pushnumber(st, num); return *this; } Lua & push_bool(bool b) { if (failed) return *this; I(lua_checkstack (st, 1)); lua_pushboolean(st, b); return *this; } Lua & push_nil() { if (failed) return *this; I(lua_checkstack (st, 1)); lua_pushnil(st); return *this; } Lua & push_table() { if (failed) return *this; I(lua_checkstack (st, 1)); lua_newtable(st); return *this; } Lua & set_table(int idx = -3) { if (failed) return *this; I(lua_checkstack (st, 1)); lua_settable(st, idx); return *this; } Lua & call(int in, int out) { if (failed) return *this; I(lua_checkstack (st, out)); if (lua_pcall(st, in, out, 0) != 0) { report_error(); } return *this; } Lua & pop(int count = 1) { if (failed) return *this; if (lua_gettop (st) < count) { fail("stack top is not >= count in pop"); return *this; } lua_pop(st, count); return *this; } Lua & func(string const & fname) { L(FL("loading lua hook %s") % fname); if (!failed) { if (missing_functions.find(fname) != missing_functions.end()) failed = true; else { push_str(fname); get_fn(); if (failed) missing_functions.insert(fname); } } return *this; } Lua & loadstring(string const & str, string const & identity) { if (!failed) { if (luaL_loadbuffer(st, str.c_str(), str.size(), identity.c_str())) { report_error(); } } return *this; } Lua & loadfile(string const & filename) { if (!failed) { if (luaL_loadfile(st, filename.c_str())) { report_error(); } } return *this; } }; std::set<string> Lua::missing_functions; extern "C" { static int monotone_mkstemp_for_lua(lua_State *L) { int fd = -1; FILE **pf = NULL; char const *filename = luaL_checkstring (L, -1); std::string dup(filename); fd = monotone_mkstemp(dup); if (fd == -1) return 0; // this magic constructs a lua object which the lua io library // will enjoy working with pf = static_cast<FILE **>(lua_newuserdata(L, sizeof(FILE *))); *pf = fdopen(fd, "r+"); lua_pushstring(L, "FILE*"); lua_rawget(L, LUA_REGISTRYINDEX); lua_setmetatable(L, -2); lua_pushstring(L, dup.c_str()); if (*pf == NULL) { lua_pushnil(L); lua_pushfstring(L, "%s", strerror(errno)); lua_pushnumber(L, errno); return 3; } else return 2; } static int monotone_existsonpath_for_lua(lua_State *L) { const char *exe = luaL_checkstring(L, -1); lua_pushnumber(L, existsonpath(exe)); return 1; } static int monotone_is_executable_for_lua(lua_State *L) { const char *path = luaL_checkstring(L, -1); lua_pushboolean(L, is_executable(path)); return 1; } static int monotone_make_executable_for_lua(lua_State *L) { const char *path = luaL_checkstring(L, -1); lua_pushnumber(L, make_executable(path)); return 1; } static int monotone_spawn_for_lua(lua_State *L) { int n = lua_gettop(L); const char *path = luaL_checkstring(L, -n); char **argv = (char**)malloc((n+1)*sizeof(char*)); int i; pid_t ret; if (argv==NULL) return 0; argv[0] = (char*)path; for (i=1; i<n; i++) argv[i] = (char*)luaL_checkstring(L, -(n - i)); argv[i] = NULL; ret = process_spawn(argv); free(argv); lua_pushnumber(L, ret); return 1; } static int monotone_wait_for_lua(lua_State *L) { pid_t pid = static_cast<pid_t>(luaL_checknumber(L, -1)); int res; int ret; ret = process_wait(pid, &res); lua_pushnumber(L, res); lua_pushnumber(L, ret); return 2; } static int monotone_kill_for_lua(lua_State *L) { int n = lua_gettop(L); pid_t pid = static_cast<pid_t>(luaL_checknumber(L, -2)); int sig; if (n>1) sig = static_cast<int>(luaL_checknumber(L, -1)); else sig = SIGTERM; lua_pushnumber(L, process_kill(pid, sig)); return 1; } static int monotone_sleep_for_lua(lua_State *L) { int seconds = static_cast<int>(luaL_checknumber(L, -1)); lua_pushnumber(L, process_sleep(seconds)); return 1; } static int monotone_guess_binary_file_contents_for_lua(lua_State *L) { const char *path = luaL_checkstring(L, -1); N(path, F("%s called with an invalid parameter") % "guess_binary"); std::ifstream file(path, ios_base::binary); if (!file) { lua_pushnil(L); return 1; } const int bufsize = 8192; char tmpbuf[bufsize]; string buf; while (file.read(tmpbuf, sizeof tmpbuf)) { I(file.gcount() <= static_cast<int>(sizeof tmpbuf)); buf.assign(tmpbuf, file.gcount()); if (guess_binary(buf)) { lua_pushboolean(L, true); return 1; } } lua_pushboolean(L, false); return 1; } static int monotone_include_for_lua(lua_State *L) { const char *path = luaL_checkstring(L, -1); N(path, F("%s called with an invalid parameter") % "Include"); bool res =Lua(L) .loadfile(std::string(path, lua_strlen(L, -1))) .call(0,1) .ok(); lua_pushboolean(L, res); return 1; } static int monotone_includedir_for_lua(lua_State *L) { const char *pathstr = luaL_checkstring(L, -1); N(pathstr, F("%s called with an invalid parameter") % "IncludeDir"); fs::path locpath(pathstr, fs::native); N(fs::exists(locpath), F("Directory '%s' does not exists") % pathstr); N(fs::is_directory(locpath), F("'%s' is not a directory") % pathstr); // directory, iterate over it, skipping subdirs, taking every filename, // sorting them and loading in sorted order fs::directory_iterator it(locpath); std::vector<fs::path> arr; while (it != fs::directory_iterator()) { if (!fs::is_directory(*it)) arr.push_back(*it); ++it; } std::sort(arr.begin(), arr.end()); for (std::vector<fs::path>::iterator i= arr.begin(); i != arr.end(); ++i) { bool res =Lua(L) .loadfile(i->string()) .call(0,1) .ok(); N(res, F("lua error while loading rcfile '%s'") % i->string()); } lua_pushboolean(L, true); return 1; } static int monotone_regex_search_for_lua(lua_State *L) { const char *re = luaL_checkstring(L, -2); const char *str = luaL_checkstring(L, -1); boost::cmatch what; bool result = false; try { result = boost::regex_search(str, what, boost::regex(re)); } catch (boost::bad_pattern e) { lua_pushstring(L, e.what()); lua_error(L); return 0; } lua_pushboolean(L, result); return 1; } static int monotone_globish_match_for_lua(lua_State *L) { const char *re = luaL_checkstring(L, -2); const char *str = luaL_checkstring(L, -1); bool result = false; try { string r(re); string n; string s(str); result = globish_matcher(r, n)(s); } catch (informative_failure & e) { lua_pushstring(L, e.what.c_str()); lua_error(L); return 0; } catch (boost::bad_pattern & e) { lua_pushstring(L, e.what()); lua_error(L); return 0; } catch (...) { lua_pushstring(L, "Unknown error."); lua_error(L); return 0; } lua_pushboolean(L, result); return 1; } static int monotone_gettext_for_lua(lua_State *L) { const char *msgid = luaL_checkstring(L, -1); lua_pushstring(L, gettext(msgid)); return 1; } static int monotone_get_confdir_for_lua(lua_State *L) { map<lua_State*, app_state*>::iterator i = map_of_lua_to_app.find(L); if (i != map_of_lua_to_app.end()) { system_path dir = i->second->get_confdir(); string confdir = dir.as_external(); lua_pushstring(L, confdir.c_str()); } else lua_pushnil(L); return 1; } static int monotone_parse_basic_io_for_lua(lua_State *L) { vector<pair<string, vector<string> > > res; const string str(luaL_checkstring(L, -1), lua_strlen(L, -1)); basic_io::input_source in(str, "monotone_parse_basic_io_for_lua"); basic_io::tokenizer tok(in); try { string got; basic_io::token_type tt; do { tt = tok.get_token(got); switch (tt) { case basic_io::TOK_SYMBOL: res.push_back(make_pair(got, vector<string>())); break; case basic_io::TOK_STRING: case basic_io::TOK_HEX: E(!res.empty(), F("bad input to parse_basic_io")); res.back().second.push_back(got); break; default: break; } } while (tt != basic_io::TOK_NONE); } catch (informative_failure & e) {// there was a syntax error in our string lua_pushnil(L); return 0; } lua_newtable(L); int n = 1; for (vector<pair<string, vector<string> > >::const_iterator i = res.begin(); i != res.end(); ++i) { lua_pushnumber(L, n++); lua_newtable(L); lua_pushstring(L, "name"); lua_pushstring(L, i->first.c_str()); lua_settable(L, -3); lua_pushstring(L, "values"); lua_newtable(L); int m = 1; for (vector<string>::const_iterator j = i->second.begin(); j != i->second.end(); ++j) { lua_pushnumber(L, m++); lua_pushstring(L, j->c_str()); lua_settable(L, -3); } lua_settable(L, -3); lua_settable(L, -3); } return 1; } } static bool run_string(lua_State * st, string const &str, string const & identity) { I(st); return Lua(st) .loadstring(str, identity) .call(0,1) .ok(); } static bool run_file(lua_State * st, string const &filename) { I(st); return Lua(st) .loadfile(filename) .call(0,1) .ok(); } lua_hooks::lua_hooks() { st = lua_open (); I(st); lua_atpanic (st, &panic_thrower); luaopen_base(st); luaopen_io(st); luaopen_string(st); luaopen_math(st); luaopen_table(st); luaopen_debug(st); // add monotone-specific functions lua_register(st, "mkstemp", monotone_mkstemp_for_lua); lua_register(st, "existsonpath", monotone_existsonpath_for_lua); lua_register(st, "is_executable", monotone_is_executable_for_lua); lua_register(st, "make_executable", monotone_make_executable_for_lua); lua_register(st, "spawn", monotone_spawn_for_lua); lua_register(st, "wait", monotone_wait_for_lua); lua_register(st, "kill", monotone_kill_for_lua); lua_register(st, "sleep", monotone_sleep_for_lua); lua_register(st, "guess_binary_file_contents", monotone_guess_binary_file_contents_for_lua); lua_register(st, "include", monotone_include_for_lua); lua_register(st, "includedir", monotone_includedir_for_lua); lua_register(st, "gettext", monotone_gettext_for_lua); lua_register(st, "get_confdir", monotone_get_confdir_for_lua); lua_register(st, "parse_basic_io", monotone_parse_basic_io_for_lua); // add regex functions: lua_newtable(st); lua_pushstring(st, "regex"); lua_pushvalue(st, -2); lua_settable(st, LUA_GLOBALSINDEX); lua_pushstring(st, "search"); lua_pushcfunction(st, monotone_regex_search_for_lua); lua_settable(st, -3); // add globish functions: lua_newtable(st); lua_pushstring(st, "globish"); lua_pushvalue(st, -2); lua_settable(st, LUA_GLOBALSINDEX); lua_pushstring(st, "match"); lua_pushcfunction(st, monotone_globish_match_for_lua); lua_settable(st, -3); lua_pop(st, 1); // Disable any functions we don't want. This is easiest // to do just by running a lua string. if (!run_string(st, "os.execute = nil " "io.popen = nil ", string("<disabled dangerous functions>"))) throw oops("lua error while disabling existing functions"); } lua_hooks::~lua_hooks() { map<lua_State*, app_state*>::iterator i = map_of_lua_to_app.find(st); if (st) lua_close (st); if (i != map_of_lua_to_app.end()) map_of_lua_to_app.erase(i); } void lua_hooks::set_app(app_state *_app) { map_of_lua_to_app.insert(make_pair(st, _app)); } #ifdef BUILD_UNIT_TESTS void lua_hooks::add_test_hooks() { if (!run_string(st, test_hooks_constant, string("<test hooks>"))) throw oops("lua error while setting up testing hooks"); } #endif void lua_hooks::add_std_hooks() { if (!run_string(st, std_hooks_constant, string("<std hooks>"))) throw oops("lua error while setting up standard hooks"); } void lua_hooks::default_rcfilename(system_path & file) { map<lua_State*, app_state*>::iterator i = map_of_lua_to_app.find(st); I(i != map_of_lua_to_app.end()); file = i->second->get_confdir() / "monotonerc"; } void lua_hooks::workspace_rcfilename(bookkeeping_path & file) { file = bookkeeping_root / "monotonerc"; } void lua_hooks::load_rcfile(utf8 const & rc) { I(st); if (rc() != "-") { fs::path locpath(system_path(rc).as_external(), fs::native); if (fs::exists(locpath) && fs::is_directory(locpath)) { // directory, iterate over it, skipping subdirs, taking every filename, // sorting them and loading in sorted order fs::directory_iterator it(locpath); std::vector<fs::path> arr; while (it != fs::directory_iterator()) { if (!fs::is_directory(*it)) arr.push_back(*it); ++it; } std::sort(arr.begin(), arr.end()); for (std::vector<fs::path>::iterator i= arr.begin(); i != arr.end(); ++i) { load_rcfile(system_path(i->native_directory_string()), true); } return; // directory read, skip the rest ... } } data dat; L(FL("opening rcfile '%s' ...\n") % rc); read_data_for_command_line(rc, dat); N(run_string(st, dat(), rc().c_str()), F("lua error while loading rcfile '%s'") % rc); L(FL("'%s' is ok\n") % rc); } void lua_hooks::load_rcfile(any_path const & rc, bool required) { I(st); if (path_exists(rc)) { L(FL("opening rcfile '%s' ...\n") % rc); N(run_file(st, rc.as_external()), F("lua error while loading '%s'") % rc); L(FL("'%s' is ok\n") % rc); } else { N(!required, F("rcfile '%s' does not exist") % rc); L(FL("skipping nonexistent rcfile '%s'\n") % rc); } } // concrete hooks // nb: if you're hooking lua to return your passphrase, you don't care if we // keep a couple extra temporaries of your passphrase around. bool lua_hooks::hook_get_passphrase(rsa_keypair_id const & k, string & phrase) { return Lua(st) .func("get_passphrase") .push_str(k()) .call(1,1) .extract_str(phrase) .ok(); } bool lua_hooks::hook_persist_phrase_ok() { bool persist_ok = false; bool executed_ok = Lua(st) .func("persist_phrase_ok") .call(0,1) .extract_bool(persist_ok) .ok(); return executed_ok && persist_ok; } bool lua_hooks::hook_expand_selector(std::string const & sel, std::string & exp) { return Lua(st) .func("expand_selector") .push_str(sel) .call(1,1) .extract_str(exp) .ok(); } bool lua_hooks::hook_expand_date(std::string const & sel, std::string & exp) { exp.clear(); bool res= Lua(st) .func("expand_date") .push_str(sel) .call(1,1) .extract_str(exp) .ok(); return res && exp.size(); } bool lua_hooks::hook_get_branch_key(cert_value const & branchname, rsa_keypair_id & k) { string key; bool ok = Lua(st) .func("get_branch_key") .push_str(branchname()) .call(1,1) .extract_str(key) .ok(); k = key; return ok; } bool lua_hooks::hook_get_author(cert_value const & branchname, string & author) { return Lua(st) .func("get_author") .push_str(branchname()) .call(1,1) .extract_str(author) .ok(); } bool lua_hooks::hook_edit_comment(string const & commentary, string const & user_log_message, string & result) { return Lua(st) .func("edit_comment") .push_str(commentary) .push_str(user_log_message) .call(2,1) .extract_str(result) .ok(); } bool lua_hooks::hook_ignore_file(file_path const & p) { bool ignore_it = false; bool exec_ok = Lua(st) .func("ignore_file") .push_str(p.as_external()) .call(1,1) .extract_bool(ignore_it) .ok(); return exec_ok && ignore_it; } bool lua_hooks::hook_ignore_branch(std::string const & branch) { bool ignore_it = false; bool exec_ok = Lua(st) .func("ignore_branch") .push_str(branch) .call(1,1) .extract_bool(ignore_it) .ok(); return exec_ok && ignore_it; } static inline bool shared_trust_function_body(Lua & ll, std::set<rsa_keypair_id> const & signers, hexenc<id> const & id, cert_name const & name, cert_value const & val) { ll.push_table(); int k = 1; for (set<rsa_keypair_id>::const_iterator v = signers.begin(); v != signers.end(); ++v) { ll.push_int(k); ll.push_str((*v)()); ll.set_table(); ++k; } bool ok; bool exec_ok = ll .push_str(id()) .push_str(name()) .push_str(val()) .call(4, 1) .extract_bool(ok) .ok(); return exec_ok && ok; } bool lua_hooks::hook_get_revision_cert_trust(std::set<rsa_keypair_id> const & signers, hexenc<id> const & id, cert_name const & name, cert_value const & val) { Lua ll(st); ll.func("get_revision_cert_trust"); return shared_trust_function_body(ll, signers, id, name, val); } bool lua_hooks::hook_get_manifest_cert_trust(std::set<rsa_keypair_id> const & signers, hexenc<id> const & id, cert_name const & name, cert_value const & val) { Lua ll(st); ll.func("get_manifest_cert_trust"); return shared_trust_function_body(ll, signers, id, name, val); } bool lua_hooks::hook_accept_testresult_change(map<rsa_keypair_id, bool> const & old_results, map<rsa_keypair_id, bool> const & new_results) { Lua ll(st); ll .func("accept_testresult_change") .push_table(); for (map<rsa_keypair_id, bool>::const_iterator i = old_results.begin(); i != old_results.end(); ++i) { ll.push_str(i->first()); ll.push_bool(i->second); ll.set_table(); } ll.push_table(); for (map<rsa_keypair_id, bool>::const_iterator i = new_results.begin(); i != new_results.end(); ++i) { ll.push_str(i->first()); ll.push_bool(i->second); ll.set_table(); } bool ok; bool exec_ok = ll .call(2, 1) .extract_bool(ok) .ok(); return exec_ok && ok; } bool lua_hooks::hook_merge3(file_path const & anc_path, file_path const & left_path, file_path const & right_path, file_path const & merged_path, data const & ancestor, data const & left, data const & right, data & result) { string res; bool ok = Lua(st) .func("merge3") .push_str(anc_path.as_external()) .push_str(left_path.as_external()) .push_str(right_path.as_external()) .push_str(merged_path.as_external()) .push_str(ancestor()) .push_str(left()) .push_str(right()) .call(7,1) .extract_str(res) .ok(); result = res; return ok; } bool lua_hooks::hook_external_diff(file_path const & path, data const & data_old, data const & data_new, bool is_binary, bool diff_args_provided, std::string const & diff_args, std::string const & oldrev, std::string const & newrev) { Lua ll(st); ll .func("external_diff") .push_str(path.as_external()); if (oldrev.length() != 0) ll.push_str(data_old()); else ll.push_nil(); ll.push_str(data_new()); ll.push_bool(is_binary); if (diff_args_provided) ll.push_str(diff_args); else ll.push_nil(); ll.push_str(oldrev); ll.push_str(newrev); return ll.call(7,0).ok(); } bool lua_hooks::hook_use_inodeprints() { bool use = false, exec_ok = false; exec_ok = Lua(st) .func("use_inodeprints") .c