# # # rename "tests/commit_validation_lua_hook" # to "tests/validate_commit_message_hook" # # add_dir "tests/validate_changes_hook" # # add_file "tests/validate_changes_hook/__driver__.lua" # content [e4b265408e42602391da60d4899afdccf0c8a720] # # add_file "tests/validate_changes_hook/commit_validate.lua" # content [b209468070d62f65c61a3c08735189c075493bd5] # # add_file "tests/validate_changes_hook/errmsg" # content [49eab8573f36c34c42b2f0d10b18f838da412761] # # patch "cmd_ws_commit.cc" # from [66094a27fe835dbad1fbb480bc50a567a150ade9] # to [004b1ff81264cfa6c47d2f39bd48642cd63d39f1] # # patch "lua_hooks.cc" # from [0e452916d1f1fda856829a1f8887fc8f25d12e05] # to [267011608ddbc8c8fcb1c0ae5a111bf24bc7ac85] # # patch "lua_hooks.hh" # from [f45c4d2aa3b19e2811fb8b7089edad3427ac9204] # to [c1748bcb8aeaef4c91477ce724f8f6cd6f53b723] # # patch "monotone.texi" # from [8f8983c5d6ae2706aa410b6520c572c746d0966c] # to [d686e3012df8f96d2ab23d83af8ec085ffa2fa7c] # ============================================================ --- tests/validate_changes_hook/__driver__.lua e4b265408e42602391da60d4899afdccf0c8a720 +++ tests/validate_changes_hook/__driver__.lua e4b265408e42602391da60d4899afdccf0c8a720 @@ -0,0 +1,13 @@ + +mtn_setup() + +check(get("commit_validate.lua")) +check(get("errmsg")) + +addfile("test", "this is a\r\ntest") +check(mtn("--rcfile=commit_validate.lua", "commit", "-m", "foo"), 1, false, true) +check(samefile("errmsg", "stderr")) + +writefile("test", "this is a\ntest") +check(mtn("--rcfile=commit_validate.lua", "commit", "-m", "foo"), 0, false, false) + ============================================================ --- tests/validate_changes_hook/commit_validate.lua b209468070d62f65c61a3c08735189c075493bd5 +++ tests/validate_changes_hook/commit_validate.lua b209468070d62f65c61a3c08735189c075493bd5 @@ -0,0 +1,18 @@ +function validate_changes(revdata, branchname) + local parsed = parse_basic_io(revdata) + for _,stanza in ipairs(parsed) do + if stanza.name == "add_file" or + stanza.name == "patch" then + local file = stanza.values[1] + if not guess_binary_file_contents(file) then + local fp = assert(io.open(file, "r")) + local contents = fp:read("*all") + fp:close() + if string.find(contents, "\r\n") then + return false, "CRLF detected" + end + end + end + end + return true, "" +end ============================================================ --- tests/validate_changes_hook/errmsg 49eab8573f36c34c42b2f0d10b18f838da412761 +++ tests/validate_changes_hook/errmsg 49eab8573f36c34c42b2f0d10b18f838da412761 @@ -0,0 +1 @@ +mtn: misuse: changes rejected by hook: CRLF detected ============================================================ --- cmd_ws_commit.cc 66094a27fe835dbad1fbb480bc50a567a150ade9 +++ cmd_ws_commit.cc 004b1ff81264cfa6c47d2f39bd48642cd63d39f1 @@ -1548,6 +1548,20 @@ CMD(commit, "commit", "ci", CMD_REF(work app.opts.branch = branchname; } + // now that we have an (unedited) branch name, let the hook decide if the + // changes should get committed at all + revision_data rev_data; + write_revision(restricted_rev, rev_data); + + bool changes_validated; + string reason; + + app.lua.hook_validate_changes(rev_data, app.opts.branch, + changes_validated, reason); + + E(changes_validated, origin::user, + F("changes rejected by hook: %s") % reason); + if (global_sanity.debug_p()) { L(FL("new manifest '%s'\n" @@ -1620,12 +1634,9 @@ CMD(commit, "commit", "ci", CMD_REF(work // If the hook doesn't exist, allow the message to be used. bool message_validated; - string reason, new_manifest_text; + reason.clear(); - revision_data new_rev; - write_revision(restricted_rev, new_rev); - - app.lua.hook_validate_commit_message(log_message, new_rev, app.opts.branch, + app.lua.hook_validate_commit_message(log_message, rev_data, app.opts.branch, message_validated, reason); E(message_validated, origin::user, F("log message rejected by hook: %s") % reason); ============================================================ --- lua_hooks.cc 0e452916d1f1fda856829a1f8887fc8f25d12e05 +++ lua_hooks.cc 267011608ddbc8c8fcb1c0ae5a111bf24bc7ac85 @@ -1074,6 +1074,25 @@ bool } bool +lua_hooks::hook_validate_changes(revision_data const & new_rev, + branch_name const & branchname, + bool & validated, + string & reason) +{ + validated = true; + return Lua(st) + .func("validate_changes") + .push_str(new_rev.inner()()) + .push_str(branchname()) + .call(2, 2) + .extract_str(reason) + // XXX When validated, the extra returned string is superfluous. + .pop() + .extract_bool(validated) + .ok(); +} + +bool lua_hooks::hook_validate_commit_message(utf8 const & message, revision_data const & new_rev, branch_name const & branchname, ============================================================ --- lua_hooks.hh f45c4d2aa3b19e2811fb8b7089edad3427ac9204 +++ lua_hooks.hh c1748bcb8aeaef4c91477ce724f8f6cd6f53b723 @@ -149,6 +149,11 @@ public: file_path const & filename); // validation hooks + bool hook_validate_changes(revision_data const & new_rev, + branch_name const & branchname, + bool & validated, + string & reason); + bool hook_validate_commit_message(utf8 const & message, revision_data const & new_rev, branch_name const & branchname, ============================================================ --- monotone.texi 8f8983c5d6ae2706aa410b6520c572c746d0966c +++ monotone.texi d686e3012df8f96d2ab23d83af8ec085ffa2fa7c @@ -1068,10 +1068,10 @@ @subsection Branch Names It has to be noted that earlier versions of monotone enforced no restrictions on branch names. Newer versions, starting with 0.99, exclude a set of control -characters though, which mostly denote either meta characters in monotone's +characters though, which mostly denote either meta characters in monotone's URI syntax or are used in globs to resolve branch patterns. These characters are @code{?}, @code{,}, @code{*}, @code{%}, @code{+}, @address@hidden, @address@hidden, @code{[}, address@hidden, @code{!} and @code{^}. Additionally, @code{-} is deprecated as first address@hidden, @code{!} and @code{^}. Additionally, @code{-} is deprecated as first character of a branch name, since it is used to denote branch exclude patterns in the aforementioned URI syntax. @@ -3751,7 +3751,7 @@ @heading Existing vars @item server-include Contains server-specific branch inclusion globs. These overrule @var{default-include-pattern} if existant and are recorded automatically -the first time you connect to a specific server or use the +the first time you connect to a specific server or use the @option{--set-default} option for a netsync operation. @item server-exclude @@ -5756,9 +5756,9 @@ @section Network databases. The @command{pull}, @command{push}, and @command{sync} commands only -require you pass @var{uri} (or @var{address} and @var{glob}) the first time +require you pass @var{uri} (or @var{address} and @var{glob}) the first time you use one of them; monotone will memorize this use and in the future default -to the same URI (or server and glob). For instance, if Bob wants to +to the same URI (or server and glob). For instance, if Bob wants to @command{sync} with Alice again, he can simply run: @smallexample @@ -5783,7 +5783,7 @@ @section Network identifier of the server in the file. This file can then be read to identify specific monotone server processes. -The syntax for patterns, both in globs as well as URIs, is very simple. +The syntax for patterns, both in globs as well as URIs, is very simple. @code{*} matches 0 or more arbitrary characters. @code{?} matches exactly 1 arbitrary character (you need to escape that with @code{%3F} in an URI). @address@hidden,bar,address@hidden matches ``foo'', or ``bar'', or ``baz''. These @@ -11639,14 +11639,29 @@ @subsection Validation Hooks allow a client to validate or reject certain behaviors. @ftable @code address@hidden validate_changes (@var{revision_text}, @var{branchname}) + +This hook is called just after the to-be-committed changes and the branch +have been logically verified and before the user enters his/her commit message. address@hidden is the full text of the changes for this revision, +which can be parsed with the parse_basic_io function. The second parameter, address@hidden, is the branch for this commit - unless it gets changed +in the editor later on. + +If the hook finds the changes satisfactory, it can return @code{true, +""}. If it finds fault, then it can return @code{false, reason} where address@hidden is the reason the changes were rejected. By default, this hook +is not defined and is thus ignored during a commit. + @item validate_commit_message (@var{message}, @var{revision_text}, @var{branchname}) This hook is called after the user has entered his/her commit message. @var{message} is the commit message that the user has entered and @var{revision_text} is the full text of the changes for this revision, -which can be parsed with the parse_basic_io function. The @var{branchname} on -which the new revision will be committed if all goes well is passed in as the third -parameter. +which, again, can be parsed with the parse_basic_io function. +The @var{branchname} on which the new revision will be committed if all goes +well is passed in as the third parameter. + If the hook finds the commit message satisfactory, it can return @code{true, ""}. If it finds fault, then it can return @code{false, reason} where @var{reason} is the reason the message was rejected. By default, this hook