summaryrefslogtreecommitdiff
path: root/plugins/git-multimail.lua
blob: b1796bfe8fe6ac867abfcc9089248e3c81d36580 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
-- 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 <dsilvers@digital-scurf.org>
-- 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