monotone

monotone Mtn Source Tree

Root/std_hooks.lua

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(namehint)
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 local filename
12 if namehint == nil then
13 filename = string.format("%s/mtn.XXXXXX", tdir)
14 else
15 filename = string.format("%s/mtn.%s.XXXXXX", tdir, namehint)
16 end
17 local name = mkstemp(filename)
18 local file = io.open(name, "r+")
19 return file, name
20end
21
22function execute(path, ...)
23 local pid
24 local ret = -1
25 pid = spawn(path, unpack(arg))
26 if (pid ~= -1) then ret, pid = wait(pid) end
27 return ret
28end
29
30-- Wrapper around execute to let user confirm in the case where a subprocess
31-- returns immediately
32-- This is needed to work around some brokenness with some merge tools
33-- (e.g. on OS X)
34function execute_confirm(path, ...)
35 ret = execute(path, unpack(arg))
36
37 if (ret ~= 0)
38 then
39 print(gettext("Press enter"))
40 else
41 print(gettext("Press enter when the subprocess has completed"))
42 end
43 io.read()
44 return ret
45end
46
47-- attributes are persistent metadata about files (such as execute
48-- bit, ACLs, various special flags) which we want to have set and
49-- re-set any time the files are modified. the attributes themselves
50-- are stored in the roster associated with the revision. each (f,k,v)
51-- attribute triple turns into a call to attr_functions[k](f,v) in lua.
52
53if (attr_init_functions == nil) then
54 attr_init_functions = {}
55end
56
57attr_init_functions["mtn:execute"] =
58 function(filename)
59 if (is_executable(filename)) then
60 return "true"
61 else
62 return nil
63 end
64 end
65
66attr_init_functions["mtn:manual_merge"] =
67 function(filename)
68 if (binary_file(filename)) then
69 return "true" -- binary files must be merged manually
70 else
71 return nil
72 end
73 end
74
75if (attr_functions == nil) then
76 attr_functions = {}
77end
78
79attr_functions["mtn:execute"] =
80 function(filename, value)
81 if (value == "true") then
82 make_executable(filename)
83 end
84 end
85
86function dir_matches(name, dir)
87 -- helper for ignore_file, matching files within dir, or dir itself.
88 -- eg for dir of 'CVS', matches CVS/, CVS/*, */CVS/ and */CVS/*
89 if (string.find(name, "^" .. dir .. "/")) then return true end
90 if (string.find(name, "^" .. dir .. "$")) then return true end
91 if (string.find(name, "/" .. dir .. "/")) then return true end
92 if (string.find(name, "/" .. dir .. "$")) then return true end
93 return false
94end
95
96function ignore_file(name)
97 -- project specific
98 if (ignored_files == nil) then
99 ignored_files = {}
100 local ignfile = io.open(".mtn-ignore", "r")
101 if (ignfile ~= nil) then
102 local line = ignfile:read()
103 while (line ~= nil) do
104 if line ~= "" then
105 table.insert(ignored_files, line)
106 end
107 line = ignfile:read()
108 end
109 io.close(ignfile)
110 end
111 end
112 for i, line in pairs(ignored_files)
113 do
114 local pcallstatus, result = pcall(function() return regex.search(line, name) end)
115 if pcallstatus == true then
116 -- no error from the regex.search call
117 if result == true then return true end
118 else
119 -- regex.search had a problem, warn the user their .mtn-ignore file syntax is wrong
120 io.stderr:write("WARNING: the line '" .. line .. "' in your .mtn-ignore file caused error '" .. result .. "'"
121 .. " while matching filename '" .. name .. "'.\nignoring this regex for all remaining files.\n")
122 table.remove(ignored_files, i)
123 end
124 end
125
126 local file_pats = {
127 -- c/c++
128 "%.a$", "%.so$", "%.o$", "%.la$", "%.lo$", "^core$",
129 "/core$", "/core%.%d+$",
130 -- java
131 "%.class$",
132 -- python
133 "%.pyc$", "%.pyo$",
134 -- gettext
135 "%.g?mo$",
136 -- intltool
137 "%.intltool%-merge%-cache$",
138 -- TeX
139 "%.aux$",
140 -- backup files
141 "%.bak$", "%.orig$", "%.rej$", "%~$",
142 -- vim creates .foo.swp files
143 "%.[^/]*%.swp$",
144 -- emacs creates #foo# files
145 "%#[^/]*%#$",
146 -- other VCSes (where metadata is stored in named files):
147 "%.scc$",
148 -- desktop/directory configuration metadata
149 "^%.DS_Store$", "/%.DS_Store$", "^desktop%.ini$", "/desktop%.ini$"
150 }
151
152 local dir_pats = {
153 -- autotools detritus:
154 "autom4te%.cache", "%.deps", "%.libs",
155 -- Cons/SCons detritus:
156 "%.consign", "%.sconsign",
157 -- other VCSes (where metadata is stored in named dirs):
158 "CVS", "%.svn", "SCCS", "_darcs", "%.cdv", "%.git", "%.bzr", "%.hg"
159 }
160
161 for _, pat in ipairs(file_pats) do
162 if string.find(name, pat) then return true end
163 end
164 for _, pat in ipairs(dir_pats) do
165 if dir_matches(name, pat) then return true end
166 end
167
168 return false;
169end
170
171-- return true means "binary", false means "text",
172-- nil means "unknown, try to guess"
173function binary_file(name)
174 -- some known binaries, return true
175 local bin_pats = {
176 "%.gif$", "%.jpe?g$", "%.png$", "%.bz2$", "%.gz$", "%.zip$",
177 "%.class$", "%.jar$", "%.war$", "%.ear$"
178 }
179
180 -- some known text, return false
181 local txt_pats = {
182 "%.cc?$", "%.cxx$", "%.hh?$", "%.hxx$", "%.cpp$", "%.hpp$",
183 "%.lua$", "%.texi$", "%.sql$", "%.java$"
184 }
185
186 local lowname=string.lower(name)
187 for _, pat in ipairs(bin_pats) do
188 if string.find(lowname, pat) then return true end
189 end
190 for _, pat in ipairs(txt_pats) do
191 if string.find(lowname, pat) then return false end
192 end
193
194 -- unknown - read file and use the guess-binary
195 -- monotone built-in function
196 return guess_binary_file_contents(name)
197end
198
199-- given a file name, return a regular expression which will match
200-- lines that name top-level constructs in that file, or "", to disable
201-- matching.
202function get_encloser_pattern(name)
203 -- texinfo has special sectioning commands
204 if (string.find(name, "%.texi$")) then
205 -- sectioning commands in texinfo: @node, @chapter, @top,
206 -- @((sub)?sub)?section, @unnumbered(((sub)?sub)?sec)?,
207 -- @appendix(((sub)?sub)?sec)?, @(|major|chap|sub(sub)?)heading
208 return ("^@("
209 .. "node|chapter|top"
210 .. "|((sub)?sub)?section"
211 .. "|(unnumbered|appendix)(((sub)?sub)?sec)?"
212 .. "|(major|chap|sub(sub)?)?heading"
213 .. ")")
214 end
215 -- LaTeX has special sectioning commands. This rule is applied to ordinary
216 -- .tex files too, since there's no reliable way to distinguish those from
217 -- latex files anyway, and there's no good pattern we could use for
218 -- arbitrary plain TeX anyway.
219 if (string.find(name, "%.tex$")
220 or string.find(name, "%.ltx$")
221 or string.find(name, "%.latex$")) then
222 return ("\\\\("
223 .. "part|chapter|paragraph|subparagraph"
224 .. "|((sub)?sub)?section"
225 .. ")")
226 end
227 -- There's no good way to find section headings in raw text, and trying
228 -- just gives distracting output, so don't even try.
229 if (string.find(name, "%.txt$")
230 or string.upper(name) == "README") then
231 return ""
232 end
233 -- This default is correct surprisingly often -- in pretty much any text
234 -- written with code-like indentation.
235 return "^[[:alnum:]$_]"
236end
237
238function edit_comment(basetext, user_log_message)
239 local exe = nil
240 if (program_exists_in_path("vi")) then exe = "vi" end
241 if (string.sub(get_ostype(), 1, 6) ~= "CYGWIN" and program_exists_in_path("notepad.exe")) then exe = "notepad.exe" end
242 local debian_editor = io.open("/usr/bin/editor")
243 if (debian_editor ~= nil) then
244 debian_editor:close()
245 exe = "/usr/bin/editor"
246 end
247 local visual = os.getenv("VISUAL")
248 if (visual ~= nil) then exe = visual end
249 local editor = os.getenv("EDITOR")
250 if (editor ~= nil) then exe = editor end
251
252 if (exe == nil) then
253 io.write("Could not find editor to enter commit message\n"
254 .. "Try setting the environment variable EDITOR\n")
255 return nil
256 end
257
258 local tmp, tname = temp_file()
259 if (tmp == nil) then return nil end
260 basetext = "MTN: " .. string.gsub(basetext, "\n", "\nMTN: ") .. "\n"
261 tmp:write(user_log_message)
262 if user_log_message == "" or string.sub(user_log_message, -1) ~= "\n" then
263 tmp:write("\n")
264 end
265 tmp:write(basetext)
266 io.close(tmp)
267
268 if (execute(exe, tname) ~= 0) then
269 io.write(string.format(gettext("Error running editor '%s' to enter log message\n"),
270 exe))
271 os.remove(tname)
272 return nil
273 end
274
275 tmp = io.open(tname, "r")
276 if (tmp == nil) then os.remove(tname); return nil end
277 local res = ""
278 local line = tmp:read()
279 while(line ~= nil) do
280 if (not string.find(line, "^MTN:")) then
281 res = res .. line .. "\n"
282 end
283 line = tmp:read()
284 end
285 io.close(tmp)
286 os.remove(tname)
287 return res
288end
289
290
291function persist_phrase_ok()
292 return true
293end
294
295
296function use_inodeprints()
297 return false
298end
299
300
301-- trust evaluation hooks
302
303function intersection(a,b)
304 local s={}
305 local t={}
306 for k,v in pairs(a) do s[v] = 1 end
307 for k,v in pairs(b) do if s[v] ~= nil then table.insert(t,v) end end
308 return t
309end
310
311function get_revision_cert_trust(signers, id, name, val)
312 return true
313end
314
315function get_manifest_cert_trust(signers, id, name, val)
316 return true
317end
318
319function get_file_cert_trust(signers, id, name, val)
320 return true
321end
322
323function accept_testresult_change(old_results, new_results)
324 local reqfile = io.open("_MTN/wanted-testresults", "r")
325 if (reqfile == nil) then return true end
326 local line = reqfile:read()
327 local required = {}
328 while (line ~= nil)
329 do
330 required[line] = true
331 line = reqfile:read()
332 end
333 io.close(reqfile)
334 for test, res in pairs(required)
335 do
336 if old_results[test] == true and new_results[test] ~= true
337 then
338 return false
339 end
340 end
341 return true
342end
343
344-- merger support
345
346-- Fields in the mergers structure:
347-- cmd : a function that performs the merge operation using the chosen
348-- program, best try.
349-- available : a function that checks that the needed program is installed and
350-- in $PATH
351-- wanted : a function that checks if the user doesn't want to use this
352-- method, and returns false if so. This should normally return
353-- true, but in some cases, especially when the merger is really
354-- an editor, the user might have a preference in EDITOR and we
355-- need to respect that.
356-- NOTE: wanted is only used when the user has NOT defined the
357-- `merger' variable or the MTN_MERGE environment variable.
358mergers = {}
359
360mergers.meld = {
361 cmd = function (tbl)
362 io.write (string.format("\nWARNING: 'meld' was choosen to perform external 3-way merge.\n"..
363 "You should merge all changes to *CENTER* file due to limitation of program\n"..
364 "arguments.\n\n"))
365 local path = "meld"
366 local ret = execute(path, tbl.lfile, tbl.afile, tbl.rfile)
367 if (ret ~= 0) then
368 io.write(string.format(gettext("Error running merger '%s'\n"), path))
369 return false
370 end
371 return tbl.afile
372 end ,
373 available = function () return program_exists_in_path("meld") end,
374 wanted = function () return true end
375}
376
377mergers.tortoise = {
378 cmd = function (tbl)
379 local path = "tortoisemerge"
380 local ret = execute(path,
381 string.format("/base:%s", tbl.afile),
382 string.format("/theirs:%s", tbl.lfile),
383 string.format("/mine:%s", tbl.rfile),
384 string.format("/merged:%s", tbl.outfile))
385 if (ret ~= 0) then
386 io.write(string.format(gettext("Error running merger '%s'\n"), path))
387 return false
388 end
389 return tbl.outfile
390 end ,
391 available = function() return program_exists_in_path ("tortoisemerge") end,
392 wanted = function () return true end
393}
394
395mergers.vim = {
396 cmd = function (tbl)
397 io.write (string.format("\nWARNING: 'vim' was choosen to perform external 3-way merge.\n"..
398 "You should merge all changes to *LEFT* file due to limitation of program\n"..
399 "arguments. The order of the files is ancestor, left, right.\n\n"))
400 local vim
401 local exec
402 if os.getenv ("DISPLAY") ~= nil and program_exists_in_path ("gvim") then
403 vim = "gvim"
404 exec = execute_confirm
405 else
406 vim = "vim"
407 exec = execute
408 end
409 local ret = exec(vim, "-f", "-d", "-c", string.format("file %s", tbl.outfile),
410 tbl.afile, tbl.lfile, tbl.rfile)
411 if (ret ~= 0) then
412 io.write(string.format(gettext("Error running merger '%s'\n"), vim))
413 return false
414 end
415 return tbl.outfile
416 end ,
417 available =
418 function ()
419 return program_exists_in_path("vim") or
420 program_exists_in_path("gvim")
421 end ,
422 wanted =
423 function ()
424 local editor = os.getenv("EDITOR")
425 if editor and
426 not (string.find(editor, "vim") or
427 string.find(editor, "gvim")) then
428 return false
429 end
430 return true
431 end
432}
433
434mergers.rcsmerge = {
435 cmd = function (tbl)
436 -- XXX: This is tough - should we check if conflict markers stay or not?
437 -- If so, we should certainly give the user some way to still force
438 -- the merge to proceed since they can appear in the files (and I saw
439 -- that). --pasky
440 local merge = os.getenv("MTN_RCSMERGE")
441 if execute(merge, tbl.lfile, tbl.afile, tbl.rfile) == 0 then
442 copy_text_file(tbl.lfile, tbl.outfile);
443 return tbl.outfile
444 end
445 local ret = execute("vim", "-f", "-c", string.format("file %s", tbl.outfile
446),
447 tbl.lfile)
448 if (ret ~= 0) then
449 io.write(string.format(gettext("Error running merger '%s'\n"), "vim"))
450 return false
451 end
452 return tbl.outfile
453 end,
454 available =
455 function ()
456 local merge = os.getenv("MTN_RCSMERGE")
457 return merge and
458 program_exists_in_path(merge) and program_exists_in_path("vim")
459 end ,
460 wanted = function () return os.getenv("MTN_RCSMERGE") ~= nil end
461}
462
463mergers.diffutils = {
464 cmd = function (tbl)
465 local ret = execute(
466 "diff3",
467 "--merge",
468 "--label", string.format("%s [left]", tbl.left_path ),
469 "--label", string.format("%s [ancestor]", tbl.anc_path ),
470 "--label", string.format("%s [right]", tbl.right_path),
471 tbl.lfile,
472 tbl.afile,
473 tbl.rfile
474 )
475 if (ret ~= 0) then
476 io.write(gettext("Error running GNU diffutils 3-way difference tool 'diff3'\n"))
477 return false
478 end
479 local ret = execute(
480 "sdiff",
481 "--diff-program=diff",
482 "--suppress-common-lines",
483 "--minimal",
484 "--output", tbl.outfile,
485 tbl.lfile,
486 tbl.rfile
487 )
488 if (ret == 2) then
489 io.write(gettext("Error running GNU diffutils 2-two merging tool 'sdiff'\n"))
490 return false
491 end
492 return tbl.outfile
493 end,
494 available =
495 function ()
496 return program_exists_in_path("diff3") and
497 program_exists_in_path("sdiff");
498 end,
499 wanted =
500 function ()
501 return true
502 end
503}
504
505mergers.emacs = {
506 cmd = function (tbl)
507 local emacs
508 if program_exists_in_path("xemacs") then
509 emacs = "xemacs"
510 else
511 emacs = "emacs"
512 end
513 local elisp = "(ediff-merge-files-with-ancestor \"%s\" \"%s\" \"%s\" nil \"%s\")"
514 local ret = execute(emacs, "--eval",
515 string.format(elisp, tbl.lfile, tbl.rfile, tbl.afile, tbl.outfile))
516 if (ret ~= 0) then
517 io.write(string.format(gettext("Error running merger '%s'\n"), emacs))
518 return false
519 end
520 return tbl.outfile
521 end,
522 available =
523 function ()
524 return program_exists_in_path("xemacs") or
525 program_exists_in_path("emacs")
526 end ,
527 wanted =
528 function ()
529 local editor = os.getenv("EDITOR")
530 if editor and
531 not (string.find(editor, "emacs") or
532 string.find(editor, "gnu")) then
533 return false
534 end
535 return true
536 end
537}
538
539mergers.xxdiff = {
540 cmd = function (tbl)
541 local path = "xxdiff"
542 local ret = execute(path,
543 "--title1", tbl.left_path,
544 "--title2", tbl.right_path,
545 "--title3", tbl.merged_path,
546 tbl.lfile, tbl.afile, tbl.rfile,
547 "--merge",
548 "--merged-filename", tbl.outfile,
549 "--exit-with-merge-status")
550 if (ret ~= 0) then
551 io.write(string.format(gettext("Error running merger '%s'\n"), path))
552 return false
553 end
554 return tbl.outfile
555 end,
556 available = function () return program_exists_in_path("xxdiff") end,
557 wanted = function () return true end
558}
559
560mergers.kdiff3 = {
561 cmd = function (tbl)
562 local path = "kdiff3"
563 local ret = execute(path,
564 "--L1", tbl.anc_path,
565 "--L2", tbl.left_path,
566 "--L3", tbl.right_path,
567 tbl.afile, tbl.lfile, tbl.rfile,
568 "--merge",
569 "--o", tbl.outfile)
570 if (ret ~= 0) then
571 io.write(string.format(gettext("Error running merger '%s'\n"), path))
572 return false
573 end
574 return tbl.outfile
575 end,
576 available = function () return program_exists_in_path("kdiff3") end,
577 wanted = function () return true end
578}
579
580mergers.opendiff = {
581 cmd = function (tbl)
582 local path = "opendiff"
583 -- As opendiff immediately returns, let user confirm manually
584 local ret = execute_confirm(path,
585 tbl.lfile,tbl.rfile,
586 "-ancestor",tbl.afile,
587 "-merge",tbl.outfile)
588 if (ret ~= 0) then
589 io.write(string.format(gettext("Error running merger '%s'\n"), path))
590 return false
591 end
592 return tbl.outfile
593 end,
594 available = function () return program_exists_in_path("opendiff") end,
595 wanted = function () return true end
596}
597
598function write_to_temporary_file(data, namehint)
599 tmp, filename = temp_file(namehint)
600 if (tmp == nil) then
601 return nil
602 end;
603 tmp:write(data)
604 io.close(tmp)
605 return filename
606end
607
608function copy_text_file(srcname, destname)
609 src = io.open(srcname, "r")
610 if (src == nil) then return nil end
611 dest = io.open(destname, "w")
612 if (dest == nil) then return nil end
613
614 while true do
615 local line = src:read()
616 if line == nil then break end
617 dest:write(line, "\n")
618 end
619
620 io.close(dest)
621 io.close(src)
622end
623
624function read_contents_of_file(filename, mode)
625 tmp = io.open(filename, mode)
626 if (tmp == nil) then
627 return nil
628 end
629 local data = tmp:read("*a")
630 io.close(tmp)
631 return data
632end
633
634function program_exists_in_path(program)
635 return existsonpath(program) == 0
636end
637
638function get_preferred_merge3_command (tbl)
639 local default_order = {"kdiff3", "xxdiff", "opendiff", "tortoise", "emacs", "vim", "meld", "diffutils"}
640 local function existmerger(name)
641 local m = mergers[name]
642 if type(m) == "table" and m.available(tbl) then
643 return m.cmd
644 end
645 return nil
646 end
647 local function trymerger(name)
648 local m = mergers[name]
649 if type(m) == "table" and m.available(tbl) and m.wanted(tbl) then
650 return m.cmd
651 end
652 return nil
653 end
654 -- Check if there's a merger given by the user.
655 local mkey = os.getenv("MTN_MERGE")
656 if not mkey then mkey = merger end
657 if not mkey and os.getenv("MTN_RCSMERGE") then mkey = "rcsmerge" end
658 -- If there was a user-given merger, see if it exists. If it does, return
659 -- the cmd function. If not, return nil.
660 local c
661 if mkey then c = existmerger(mkey) end
662 if c then return c,mkey end
663 if mkey then return nil,mkey end
664 -- If there wasn't any user-given merger, take the first that's available
665 -- and wanted.
666 for _,mkey in ipairs(default_order) do
667 c = trymerger(mkey) ; if c then return c,nil end
668 end
669end
670
671function merge3 (anc_path, left_path, right_path, merged_path, ancestor, left, right)
672 local ret = nil
673 local tbl = {}
674
675 tbl.anc_path = anc_path
676 tbl.left_path = left_path
677 tbl.right_path = right_path
678
679 tbl.merged_path = merged_path
680 tbl.afile = nil
681 tbl.lfile = nil
682 tbl.rfile = nil
683 tbl.outfile = nil
684 tbl.meld_exists = false
685 tbl.lfile = write_to_temporary_file (left, "left")
686 tbl.afile = write_to_temporary_file (ancestor, "ancestor")
687 tbl.rfile = write_to_temporary_file (right, "right")
688 tbl.outfile = write_to_temporary_file ("", "merged")
689
690 if tbl.lfile ~= nil and tbl.rfile ~= nil and tbl.afile ~= nil and tbl.outfile ~= nil
691 then
692 local cmd,mkey = get_preferred_merge3_command (tbl)
693 if cmd ~=nil
694 then
695 io.write (string.format(gettext("executing external 3-way merge command\n")))
696 ret = cmd (tbl)
697 if not ret then
698 ret = nil
699 else
700 ret = read_contents_of_file (ret, "r")
701 if string.len (ret) == 0
702 then
703 ret = nil
704 end
705 end
706 else
707 if mkey then
708 io.write (string.format("The possible commands for the "..mkey.." merger aren't available.\n"..
709 "You may want to check that $MTN_MERGE or the lua variable `merger' is set\n"..
710 "to something available. If you want to use vim or emacs, you can also\n"..
711"set $EDITOR to something appropriate"))
712 else
713 io.write (string.format("No external 3-way merge command found.\n"..
714 "You may want to check that $EDITOR is set to an editor that supports 3-way\n"..
715 "merge, set this explicitly in your get_preferred_merge3_command hook,\n"..
716 "or add a 3-way merge program to your path.\n\n"))
717 end
718 end
719 end
720
721 os.remove (tbl.lfile)
722 os.remove (tbl.rfile)
723 os.remove (tbl.afile)
724 os.remove (tbl.outfile)
725
726 return ret
727end
728
729-- expansion of values used in selector completion
730
731function expand_selector(str)
732
733 -- something which looks like a generic cert pattern
734 if string.find(str, "^[^=]*=.*$")
735 then
736 return ("c:" .. str)
737 end
738
739 -- something which looks like an email address
740 if string.find(str, "[%w%-_]+@[%w%-_]+")
741 then
742 return ("a:" .. str)
743 end
744
745 -- something which looks like a branch name
746 if string.find(str, "[%w%-]+%.[%w%-]+")
747 then
748 return ("b:" .. str)
749 end
750
751 -- a sequence of nothing but hex digits
752 if string.find(str, "^%x+$")
753 then
754 return ("i:" .. str)
755 end
756
757 -- tries to expand as a date
758 local dtstr = expand_date(str)
759 if dtstr ~= nil
760 then
761 return ("d:" .. dtstr)
762 end
763
764 return nil
765end
766
767-- expansion of a date expression
768function expand_date(str)
769 -- simple date patterns
770 if string.find(str, "^19%d%d%-%d%d")
771 or string.find(str, "^20%d%d%-%d%d")
772 then
773 return (str)
774 end
775
776 -- "now"
777 if str == "now"
778 then
779 local t = os.time(os.date('!*t'))
780 return os.date("%FT%T", t)
781 end
782
783 -- today don't uses the time # for xgettext's sake, an extra quote
784 if str == "today"
785 then
786 local t = os.time(os.date('!*t'))
787 return os.date("%F", t)
788 end
789
790 -- "yesterday", the source of all hangovers
791 if str == "yesterday"
792 then
793 local t = os.time(os.date('!*t'))
794 return os.date("%F", t - 86400)
795 end
796
797 -- "CVS style" relative dates such as "3 weeks ago"
798 local trans = {
799 minute = 60;
800 hour = 3600;
801 day = 86400;
802 week = 604800;
803 month = 2678400;
804 year = 31536000
805 }
806 local pos, len, n, type = string.find(str, "(%d+) ([minutehordaywk]+)s? ago")
807 if trans[type] ~= nil
808 then
809 local t = os.time(os.date('!*t'))
810 if trans[type] <= 3600
811 then
812 return os.date("%FT%T", t - (n * trans[type]))
813 else
814 return os.date("%F", t - (n * trans[type]))
815 end
816 end
817
818 return nil
819end
820
821
822external_diff_default_args = "-u"
823
824-- default external diff, works for gnu diff
825function external_diff(file_path, data_old, data_new, is_binary, diff_args, rev_old, rev_new)
826 local old_file = write_to_temporary_file(data_old);
827 local new_file = write_to_temporary_file(data_new);
828
829 if diff_args == nil then diff_args = external_diff_default_args end
830 execute("diff", diff_args, "--label", file_path .. "\told", old_file, "--label", file_path .. "\tnew", new_file);
831
832 os.remove (old_file);
833 os.remove (new_file);
834end
835
836-- netsync permissions hooks (and helper)
837
838function globish_match(glob, str)
839 local pcallstatus, result = pcall(function() if (globish.match(glob, str)) then return true else return false end end)
840 if pcallstatus == true then
841 -- no error
842 return result
843 else
844 -- globish.match had a problem
845 return nil
846 end
847end
848
849function get_netsync_read_permitted(branch, ident)
850 local permfile = io.open(get_confdir() .. "/read-permissions", "r")
851 if (permfile == nil) then return false end
852 local dat = permfile:read("*a")
853 io.close(permfile)
854 local res = parse_basic_io(dat)
855 if res == nil then
856 io.stderr:write("file read-permissions cannot be parsed\n")
857 return false
858 end
859 local matches = false
860 local cont = false
861 for i, item in pairs(res)
862 do
863 -- legal names: pattern, allow, deny, continue
864 if item.name == "pattern" then
865 if matches and not cont then return false end
866 matches = false
867 cont = false
868 for j, val in pairs(item.values) do
869 if globish_match(val, branch) then matches = true end
870 end
871 elseif item.name == "allow" then if matches then
872 for j, val in pairs(item.values) do
873 if val == "*" then return true end
874 if val == "" and ident == nil then return true end
875 if globish_match(val, ident) then return true end
876 end
877 end elseif item.name == "deny" then if matches then
878 for j, val in pairs(item.values) do
879 if val == "*" then return false end
880 if val == "" and ident == nil then return false end
881 if globish_match(val, ident) then return false end
882 end
883 end elseif item.name == "continue" then if matches then
884 cont = true
885 for j, val in pairs(item.values) do
886 if val == "false" or val == "no" then cont = false end
887 end
888 end elseif item.name ~= "comment" then
889 io.stderr:write("unknown symbol in read-permissions: " .. item.name .. "\n")
890 return false
891 end
892 end
893 return false
894end
895
896function get_netsync_write_permitted(ident)
897 local permfile = io.open(get_confdir() .. "/write-permissions", "r")
898 if (permfile == nil) then
899 return false
900 end
901 local matches = false
902 local line = permfile:read()
903 while (not matches and line ~= nil) do
904 local _, _, ln = string.find(line, "%s*([^%s]*)%s*")
905 if ln == "*" then matches = true end
906 if globish_match(ln, ident) then matches = true end
907 line = permfile:read()
908 end
909 io.close(permfile)
910 return matches
911end
912
913-- This is a simple function which assumes you're going to be spawning
914-- a copy of mtn, so reuses a common bit at the end for converting
915-- local args into remote args. You might need to massage the logic a
916-- bit if this doesn't fit your assumptions.
917
918function get_netsync_connect_command(uri, args)
919
920 local argv = nil
921
922 if uri["scheme"] == "ssh"
923 and uri["host"]
924 and uri["path"] then
925
926 argv = { "ssh" }
927 if uri["user"] then
928 table.insert(argv, "-l")
929 table.insert(argv, uri["user"])
930 end
931 if uri["port"] then
932 table.insert(argv, "-p")
933 table.insert(argv, uri["port"])
934 end
935
936 -- ssh://host/~/dir/file.mtn or
937 -- ssh://host/~user/dir/file.mtn should be home-relative
938 if string.find(uri["path"], "^/~") then
939 uri["path"] = string.sub(uri["path"], 2)
940 end
941
942 table.insert(argv, uri["host"])
943 end
944
945 if uri["scheme"] == "file" and uri["path"] then
946 argv = { }
947 end
948
949 if argv then
950
951 table.insert(argv, get_mtn_command(uri["host"]))
952
953 if args["debug"] then
954 table.insert(argv, "--debug")
955 else
956 table.insert(argv, "--quiet")
957 end
958
959 table.insert(argv, "--db")
960 table.insert(argv, uri["path"])
961 table.insert(argv, "serve")
962 table.insert(argv, "--stdio")
963 table.insert(argv, "--no-transport-auth")
964
965 end
966 return argv
967end
968
969function use_transport_auth(uri)
970 if uri["scheme"] == "ssh"
971 or uri["scheme"] == "file" then
972 return false
973 else
974 return true
975 end
976end
977
978function get_mtn_command(host)
979 return "mtn"
980end

Archive Download this file

Branches

Tags

Quick Links:     www.monotone.ca    -     Downloads    -     Documentation    -     Wiki    -     Code Forge    -     Build Status