1
2-- this is the standard set of lua hooks for monotone;
3-- user-provided files can override it or add to it.
4
5function temp_file()
6   local tdir
7   tdir = os.getenv("TMPDIR")
8   if tdir == nil then tdir = os.getenv("TMP") end
9   if tdir == nil then tdir = os.getenv("TEMP") end
10   if tdir == nil then tdir = "/tmp" end
11   return mkstemp(string.format("%s/mt.XXXXXX", tdir))
12end
13
14function execute(path, ...)
15   local pid = posix.fork()
16   local ret = -1
17   if pid == 0 then
18      posix.exec(path, unpack(arg))
19   else
20      ret, pid = posix.wait(pid)
21   end
22   return ret
23end
24
25
26
27-- attributes are persistent metadata about files (such as execute
28-- bit, ACLs, various special flags) which we want to have set and
29-- re-set any time the files are modified. the attributes themselves
30-- are stored in a file .mt-attrs, in the working copy (and
31-- manifest). each (f,k,v) triple in an atribute file turns into a
32-- call to attr_functions[k](f,v) in lua.
33
34if (attr_functions == nil) then
35   attr_functions = {}
36end
37
38
39attr_functions["execute"] =
40   function(filename, value)
41      if (value == "true") then
42         posix.chmod(filename, "u+x")
43      end
44   end
45
46
47function ignore_file(name)
48   if (string.find(name, "%.a$")) then return true end
49   if (string.find(name, "%.so$")) then return true end
50   if (string.find(name, "%.o$")) then return true end
51   if (string.find(name, "%.la$")) then return true end
52   if (string.find(name, "%.lo$")) then return true end
53   if (string.find(name, "%.aux$")) then return true end
54   if (string.find(name, "%.bak$")) then return true end
55   if (string.find(name, "%.orig$")) then return true end
56   if (string.find(name, "%.rej$")) then return true end
57   if (string.find(name, "%~$")) then return true end
58   if (string.find(name, "/core$")) then return true end
59   if (string.find(name, "^CVS/")) then return true end
60   if (string.find(name, "/CVS/")) then return true end
61   if (string.find(name, "^%.svn/")) then return true end
62   if (string.find(name, "/%.svn/")) then return true end
63   if (string.find(name, "^SCCS/")) then return true end
64   if (string.find(name, "/SCCS/")) then return true end
65   return false;
66end
67
68
69function edit_comment(basetext)
70   local exe = "vi"
71   local visual = os.getenv("VISUAL")
72   if (visual ~= nil) then exe = visual end
73   local editor = os.getenv("EDITOR")
74   if (editor ~= nil) then exe = editor end
75
76   local tmp, tname = temp_file()
77   if (tmp == nil) then return nil end
78   basetext = "MT: " .. string.gsub(basetext, "\n", "\nMT: ") .. "\n"
79   tmp:write(basetext)
80   io.close(tmp)
81
82   if (execute(exe, tname) ~= 0) then
83      os.remove(tname)
84      return nil
85   end
86
87   tmp = io.open(tname, "r")
88   if (tmp == nil) then os.remove(tname); return nil end
89   local res = ""
90   local line = tmp:read()
91   while(line ~= nil) do
92      if (not string.find(line, "^MT:")) then
93         res = res .. line .. "\n"
94      end
95      line = tmp:read()
96   end
97   io.close(tmp)
98   os.remove(tname)
99   return res
100end
101
102
103function non_blocking_rng_ok()
104   return true
105end
106
107
108function persist_phrase_ok()
109   return true
110end
111
112-- trust evaluation hooks
113
114function intersection(a,b)
115   local s={}
116   local t={}
117   for k,v in pairs(a) do s[v] = 1 end
118   for k,v in pairs(b) do if s[v] ~= nil then table.insert(t,v) end end
119   return t
120end
121
122function get_revision_cert_trust(signers, id, name, val)
123   return true
124end
125
126function get_manifest_cert_trust(signers, id, name, val)
127   return true
128end
129
130function get_file_cert_trust(signers, id, name, val)
131   return true
132end
133
134function accept_testresult_change(old_results, new_results)
135   for test,res in pairs(old_results)
136   do
137      if res == true and new_results[test] ~= true
138      then
139         return false
140      end
141   end
142   return true
143end
144
145-- merger support
146
147function merge2_meld_cmd(lfile, rfile)
148   return
149   function()
150      return execute("meld", lfile, rfile)
151   end
152end
153
154function merge3_meld_cmd(lfile, afile, rfile)
155   return
156   function()
157      return execute("meld", lfile, afile, rfile)
158   end
159end
160
161
162function merge2_vim_cmd(vim, lfile, rfile, outfile)
163   return
164   function()
165      return execute(vim, "-f", "-d", "-c", string.format("file %s", outfile),
166                     lfile, rfile)
167   end
168end
169
170function merge3_vim_cmd(vim, lfile, afile, rfile, outfile)
171   return
172   function()
173      return execute(vim, "-f", "-d", "-c", string.format("file %s", outfile),
174                     lfile, afile, rfile)
175   end
176end
177
178function merge2_emacs_cmd(emacs, lfile, rfile, outfile)
179   local elisp = "(ediff-merge-files \"%s\" \"%s\" nil \"%s\")"
180   return
181   function()
182      return execute(emacs, "-no-init-file", "-eval",
183                     string.format(elisp, lfile, rfile, outfile))
184   end
185end
186
187function merge3_emacs_cmd(emacs, lfile, afile, rfile, outfile)
188   local elisp = "(ediff-merge-files-with-ancestor \"%s\" \"%s\" \"%s\" nil \"%s\")"
189   local cmd_fmt = "%s -no-init-file -eval " .. elisp
190   return
191   function()
192      execute(emacs, "-no-init-file", "-eval",
193              string.format(elisp, lfile, rfile, afile, outfile))
194   end
195end
196
197function merge2_xxdiff_cmd(left_path, right_path, merged_path, lfile, rfile, outfile)
198   return
199   function()
200      return execute("xxdiff",
201                     "--title1", left_path,
202                     "--title2", right_path,
203                     lfile, rfile,
204                     "--merged-filename", outfile)
205   end
206end
207
208function merge3_xxdiff_cmd(left_path, anc_path, right_path, merged_path,
209                           lfile, afile, rfile, outfile)
210   return
211   function()
212      return execute("xxdiff",
213                     "--title1", left_path,
214                     "--title2", right_path,
215                     "--title3", merged_path,
216                     lfile, afile, rfile,
217                     "--merge",
218                     "--merged-filename", outfile)
219   end
220end
221
222function write_to_temporary_file(data)
223   tmp, filename = temp_file()
224   if (tmp == nil) then
225      return nil
226   end;
227   tmp:write(data)
228   io.close(tmp)
229   return filename
230end
231
232function read_contents_of_file(filename)
233   tmp = io.open(filename, "r")
234   if (tmp == nil) then
235      return nil
236   end
237   local data = tmp:read("*a")
238   io.close(tmp)
239   return data
240end
241
242function program_exists_in_path(program)
243   return execute("which", program) == 0
244end
245
246function merge2(left_path, right_path, merged_path, left, right)
247   local lfile = nil
248   local rfile = nil
249   local outfile = nil
250   local data = nil
251   local meld_exists = false
252
253   lfile = write_to_temporary_file(left)
254   rfile = write_to_temporary_file(right)
255   outfile = write_to_temporary_file("")
256
257   if lfile ~= nil and
258      rfile ~= nil and
259      outfile ~= nil
260   then
261      local cmd = nil
262      if program_exists_in_path("meld") then
263         meld_exists = true
264         io.write(string.format("\nWARNING: 'meld' was choosen to perform external 2-way merge.\n" ..
265                                "You should merge all changes to *LEFT* file due to limitation of program\n" ..
266                                   "arguments.\n\n"))
267         cmd = merge2_meld_cmd(lfile, rfile)
268      elseif program_exists_in_path("xxdiff") then
269         cmd = merge2_xxdiff_cmd(left_path, right_path, merged_path,
270                                 lfile, rfile, outfile)
271      elseif program_exists_in_path("emacs") then
272         cmd = merge2_emacs_cmd("emacs", lfile, rfile, outfile)
273      elseif program_exists_in_path("xemacs") then
274         cmd = merge2_emacs_cmd("xemacs", lfile, rfile, outfile)
275      elseif program_exists_in_path("gvim") then
276         cmd = merge2_vim_cmd("gvim", lfile, rfile, outfile)
277      elseif program_exists_in_path("vim") then
278         cmd = merge2_vim_cmd("vim", lfile, rfile, outfile)
279      end
280
281      if cmd ~= nil
282      then
283         io.write(string.format("executing external 2-way merge command\n"))
284         cmd()
285         if meld_exists then
286            data = read_contents_of_file(lfile)
287         else
288            data = read_contents_of_file(outfile)
289         end
290         if string.len(data) == 0
291         then
292            data = nil
293         end
294      else
295         io.write("no external 2-way merge command found\n")
296      end
297   end
298
299   os.remove(lfile)
300   os.remove(rfile)
301   os.remove(outfile)
302
303   return data
304end
305
306function merge3(anc_path, left_path, right_path, merged_path, ancestor, left, right)
307   local afile = nil
308   local lfile = nil
309   local rfile = nil
310   local outfile = nil
311   local data = nil
312   local meld_exists = false
313
314   lfile = write_to_temporary_file(left)
315   afile = write_to_temporary_file(ancestor)
316   rfile = write_to_temporary_file(right)
317   outfile = write_to_temporary_file("")
318
319   if lfile ~= nil and
320      rfile ~= nil and
321      afile ~= nil and
322      outfile ~= nil
323   then
324      local cmd = nil
325      if program_exists_in_path("meld") then
326         meld_exists = true
327         io.write(string.format("\nWARNING: 'meld' was choosen to perform external 3-way merge.\n" ..
328                                "You should merge all changes to *CENTER* file due to limitation of program\n" ..
329                                   "arguments.\n\n"))
330         cmd = merge3_meld_cmd(lfile, afile, rfile)
331      elseif program_exists_in_path("xxdiff") then
332         cmd = merge3_xxdiff_cmd(left_path, anc_path, right_path, merged_path,
333                                 lfile, afile, rfile, outfile)
334      elseif program_exists_in_path("emacs") then
335         cmd = merge3_emacs_cmd("emacs", lfile, afile, rfile, outfile)
336      elseif program_exists_in_path("xemacs") then
337         cmd = merge3_emacs_cmd("xemacs", lfile, afile, rfile, outfile)
338      elseif program_exists_in_path("gvim") then
339         cmd = merge3_vim_cmd("gvim", lfile, afile, rfile, outfile)
340      elseif program_exists_in_path("vim") then
341         cmd = merge3_vim_cmd("vim", lfile, afile, rfile, outfile)
342      end
343
344      if cmd ~= nil
345      then
346         io.write(string.format("executing external 3-way merge command\n"))
347         cmd()
348         if meld_exists then
349            data = read_contents_of_file(afile)
350         else
351            data = read_contents_of_file(outfile)
352         end
353         if string.len(data) == 0
354         then
355            data = nil
356         end
357      else
358         io.write("no external 3-way merge command found\n")
359      end
360   end
361
362   os.remove(lfile)
363   os.remove(rfile)
364   os.remove(afile)
365   os.remove(outfile)
366
367   return data
368end
369
370
371-- expansion of values used in selector completion
372
373function expand_selector(str)
374
375   -- simple date patterns
376   if string.find(str, "^19%d%d%-%d%d")
377      or string.find(str, "^20%d%d%-%d%d")
378   then
379      return ("d:" .. str)
380   end
381
382   -- something which looks like an email address
383   if string.find(str, "[%w%-_]+@[%w%-_]+")
384   then
385      return ("a:" .. str)
386   end
387
388   -- something which looks like a branch name
389   if string.find(str, "[%w%-]+%.[%w%-]+")
390   then
391      return ("b:" .. str)
392   end
393
394   -- a sequence of nothing but hex digits
395   if string.find(str, "^%x+$")
396   then
397      return ("i:" .. str)
398   end
399
400   -- "yesterday", the source of all hangovers
401   if str == "yesterday"
402   then
403      local t = os.time(os.date('!*t'))
404      return os.date("d:%F", t - 86400)
405   end
406
407   -- "CVS style" relative dates such as "3 weeks ago"
408   local trans = {
409      minute = 60;
410      hour = 3600;
411      day = 86400;
412      week = 604800;
413      month = 2678400;
414      year = 31536000
415   }
416   local pos, len, n, type = string.find(str, "(%d+) ([minutehordaywk]+)s? ago")
417   if trans[type] ~= nil
418   then
419      local t = os.time(os.date('!*t'))
420      return os.date("d:%F", t - (n * trans[type]))
421   end
422
423   return nil
424end
425