-- Git Multimail Plugin -- -- This plugin enables support for git-multimail on the server-side which -- allows for the nice(r) reporting of changes by email. -- -- Copyright 2017 Daniel Silverstone -- All rights reserved. -- -- Redistribution and use in source and binary forms, with or without -- modification, are permitted provided that the following conditions -- are met: -- 1. Redistributions of source code must retain the above copyright -- notice, this list of conditions and the following disclaimer. -- 2. Redistributions in binary form must reproduce the above copyright -- notice, this list of conditions and the following disclaimer in the -- documentation and/or other materials provided with the distribution. -- 3. Neither the name of the author nor the names of their contributors -- may be used to endorse or promote products derived from this software -- without specific prior written permission. -- -- THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND -- ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -- IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -- ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE -- FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -- DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS -- OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) -- HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -- LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY -- OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF -- SUCH DAMAGE. local gitano = require "gitano" local luxio = require "luxio" local sp = require "luxio.subprocess" local git_multimail_py = nil do local args = { "git", "config", "--path", "gitano.multimail.location", stdout = sp.PIPE, cwd = luxio.getenv("HOME"), } local proc = sp.spawn_simple(args) git_multimail_py = proc.stdout:read("*l") local how, why = proc:wait() if how ~= "exit" then return -- don't register, something really went wrong end if why ~= 0 then git_multimail_py = nil end end local GLOBAL_CONFS = { environment = "generic", refFilterExclusionRegex = "^refs/gitano/", verbose = "true", } local function run_multimail(confbits, env, stdin) local args = { env = env, stdin = sp.PIPE, stdout = sp.PIPE, stderr = sp.PIPE, git_multimail_py, } for k, v in pairs(confbits) do args[#args+1] = "-c" args[#args+1] = "multimailhook." .. k .. "=" .. v end gitano.log.ddebug("About to spawn git_multimail.py as...") for _, v in ipairs(args) do gitano.log.ddebug("=> " .. v) end local proc = sp.spawn_simple(args) proc.stdin:write(stdin) proc.stdin:close() local outcontent = proc.stdout:read("*a") local errcontent = proc.stderr:read("*a") local how, why = proc:wait() local function show(what, pfx) if what == "" then return end if what:sub(-1) ~= "\n" then what = what .. "\n" end for line in what:gfind("([^\n]*)\n") do gitano.log.state(pfx .. ": " .. line) end end if outcontent ~= "" or errcontent ~= "" then gitano.log.state("Git Multimail says:") show(outcontent, "O") show(errcontent, "E") end if (how ~= "exit") or (why ~= 0) then gitano.log.error("git_multimail.py failed: " .. tostring(how) .. " " .. tostring(why)) end gitano.log.ddebug("Done with multimail") end local function git_multimail_post_receive_hook(repo, updates) -- We don't get here at all if we don't have git_multimail.py's location -- as such, our first job is to request authorisation from the ACL local context = { user = "gitano/multimail", source = "plugin_multimail", operation = "multimail", } gitano.log.ddebug("Multimail: Asking " .. repo.name .. " if multimail is allowed...") local action, reason = repo:run_lace(context) gitano.log.ddebug(("Multimail: action=%q reason=%q"):format(action,reason)) if action ~= "allow" then gitano.log.info("Multimail disallowed: " .. tostring(action) .. ": " .. tostring(reason)) return "continue" end -- Okay, we're allowed to run, so let's build our config... local confbits = gitano.util.deep_copy(GLOBAL_CONFS) local conf = repo.config local username = luxio.getenv("GITANO_USER") local user = conf.users[username] or { real_name = "Anonymous", email_address = "postmaster@localhost.localdomain" } -- And we need to set 'from' confbits.from = user.real_name .. " <" .. user.email_address .. ">" local function layer_confbits(fromrepo, pfx) gitano.log.ddebug("Multimail: Attempting to layer from " .. tostring(fromrepo.name) .. " via prefix " .. pfx) -- Note this is currently an unstable API local from = fromrepo.project_config for key, value in from:each(pfx) do key = key:sub(#pfx+2,-1) gitano.log.ddebug(("Multimail: => setting %q to %q"):format(key,value)) confbits[key] = tostring(value) end end local gitano_admin_repo = ( gitano.repository.find(repo.config, "gitano-admin.git")) layer_confbits(gitano_admin_repo, "defaults.multimail") layer_confbits(repo, "multimail") layer_confbits(gitano_admin_repo, "override.multimail") -- Now we run a supple hook (if necessary) which will be able to adjust -- the config bits... if repo:uses_hook("multimail") then gitano.log.debug("Multimail: Configuring supple for hook") gitano.actions.set_supple_globals("multimail") gitano.log.info("Multimail: Running hook") local info = { username = username, source = "plugin_multimail", realname = user.real_name or "", email = user.email_address or "", } local ok, msg = gitano.supple.run_hook("multimail", repo, info, updates, confbits) if not ok then gitano.log.error("Multimail: Hook failed: " .. tostring(msg)) gitano.log.error("Multimail: Aborting send") return end gitano.log.info("Multimail: Finished running hook, continuing...") end -- Next we clean up the config bits, by copying over only what we whitelist local unfiltered = confbits confbits = {} for _, key in ipairs( { "mailingList", "refchangeList", "announceList", "commitList", "from", "announceShortLog", "commitBrowseURL", "refchangeShowGraph", "refchangeShowLog", "administrator", "emailPrefix", "refFilterInclusionRegex", "refFilterExclusionRegex", "refFilterDoSendRegex", "refFilterDontSendRegex", "verbose", "stdout", }) do local val = unfiltered[key] if val and val ~= "" then gitano.log.ddebug( ("Multimail: Permitted %q => %q"):format(key, unfiltered[key])) confbits[key] = unfiltered[key] end end -- No matter what goes on in the clod configs, we need to set repoName confbits.repoName = repo.name local fullusername = user.real_name .. " (" .. username .. ")" local env = { USERNAME = fullusername, USER = fullusername, } -- Prepare the stdin for git_multimail.py, in post-receive format local stdin = {} for ref, chg in pairs(updates) do stdin[#stdin+1] = table.concat({chg.oldsha, chg.newsha, ref}, " ") end stdin = table.concat(stdin, "\n") .. "\n" -- Finally, if there's any address set at all, run multimail if (confbits.mailingList or confbits.announceList or confbits.commitList or confbits.refchangeList) then run_multimail(confbits, env, stdin) else gitano.log.info("Multimail: Did not run, no target addresses set") end return "continue" end if git_multimail_py ~= nil and luxio.getenv("GITANO_USER") ~= "gitano/bypass" then gitano.hooks.add(gitano.hooks.names.POST_RECEIVE, 100, git_multimail_post_receive_hook) end