/usr/share/doc/monotone/html/Default-hooks.html is in monotone-doc 1.0-3.
This file is owned by root:root, with mode 0o644.
The actual contents of the file can be viewed below.
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 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 1422 1423 1424 1425 1426 1427 1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 1438 1439 1440 1441 1442 1443 1444 1445 1446 1447 1448 1449 1450 1451 1452 1453 1454 1455 1456 1457 1458 1459 1460 1461 1462 1463 1464 1465 1466 1467 1468 1469 1470 1471 1472 1473 1474 1475 1476 1477 1478 1479 1480 1481 1482 1483 1484 1485 1486 1487 1488 1489 1490 1491 1492 1493 1494 1495 1496 1497 1498 1499 1500 1501 1502 1503 1504 1505 1506 1507 1508 1509 1510 1511 1512 1513 1514 1515 1516 1517 1518 1519 1520 1521 1522 1523 1524 1525 1526 1527 1528 1529 1530 1531 1532 1533 1534 1535 1536 1537 1538 1539 1540 1541 1542 1543 1544 1545 1546 1547 1548 1549 1550 1551 1552 1553 1554 1555 1556 1557 1558 1559 1560 1561 1562 1563 1564 1565 1566 1567 1568 1569 1570 1571 1572 1573 1574 1575 1576 1577 1578 1579 1580 1581 1582 1583 1584 1585 1586 1587 1588 | <html lang="en">
<head>
<title>Default hooks - monotone documentation</title>
<meta http-equiv="Content-Type" content="text/html">
<meta name="description" content="monotone documentation">
<meta name="generator" content="makeinfo 4.13">
<link title="Top" rel="start" href="index.html#Top">
<link rel="prev" href="Special-Topics.html#Special-Topics" title="Special Topics">
<link rel="next" href="General-Index.html#General-Index" title="General Index">
<link href="http://www.gnu.org/software/texinfo/" rel="generator-home" title="Texinfo Homepage">
<meta http-equiv="Content-Style-Type" content="text/css">
<style type="text/css"><!--
pre.display { font-family:inherit }
pre.format { font-family:inherit }
pre.smalldisplay { font-family:inherit; font-size:smaller }
pre.smallformat { font-family:inherit; font-size:smaller }
pre.smallexample { font-size:smaller }
pre.smalllisp { font-size:smaller }
span.sc { font-variant:small-caps }
span.roman { font-family:serif; font-weight:normal; }
span.sansserif { font-family:sans-serif; font-weight:normal; }
--></style>
<link rel="stylesheet" type="text/css" href="texinfo.css">
</head>
<body>
<div class="node">
<a name="Default-hooks"></a>
<p>
Next: <a rel="next" accesskey="n" href="General-Index.html#General-Index">General Index</a>,
Previous: <a rel="previous" accesskey="p" href="Special-Topics.html#Special-Topics">Special Topics</a>,
Up: <a rel="up" accesskey="u" href="index.html#Top">Top</a>
<hr>
</div>
<h2 class="appendix">Appendix A Default hooks</h2>
<p>This section contains the entire source code of the standard hook file,
that is built in to the monotone executable, and read before any user
hooks files (unless <samp><span class="option">--no-builtin-rcfiles</span></samp> is passed). It contains the
default values for all hooks. See <a href="rcfiles.html#rcfiles">rcfiles</a>.
<!-- note that this requires -I $(top_srcdir)/src on the makeinfo -->
<!-- command line, and $(top_srcdir)/src in TEXINPUTS for texi2dvi -->
<pre class="verbatim">-- Copyright (C) 2003 Graydon Hoare <graydon@pobox.com>
--
-- This program is made available under the GNU GPL version 2.0 or
-- greater. See the accompanying file COPYING for details.
--
-- This program is distributed WITHOUT ANY WARRANTY; without even the
-- implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
-- PURPOSE.
-- this is the standard set of lua hooks for monotone;
-- user-provided files can override it or add to it.
function temp_file(namehint)
local tdir
tdir = os.getenv("TMPDIR")
if tdir == nil then tdir = os.getenv("TMP") end
if tdir == nil then tdir = os.getenv("TEMP") end
if tdir == nil then tdir = "/tmp" end
local filename
if namehint == nil then
filename = string.format("%s/mtn.XXXXXX", tdir)
else
filename = string.format("%s/mtn.%s.XXXXXX", tdir, namehint)
end
local name = mkstemp(filename)
local file = io.open(name, "r+")
return file, name
end
function execute(path, ...)
local pid
local ret = -1
pid = spawn(path, ...)
if (pid ~= -1) then ret, pid = wait(pid) end
return ret
end
function execute_redirected(stdin, stdout, stderr, path, ...)
local pid
local ret = -1
io.flush();
pid = spawn_redirected(stdin, stdout, stderr, path, ...)
if (pid ~= -1) then ret, pid = wait(pid) end
return ret
end
-- Wrapper around execute to let user confirm in the case where a subprocess
-- returns immediately
-- This is needed to work around some brokenness with some merge tools
-- (e.g. on OS X)
function execute_confirm(path, ...)
ret = execute(path, ...)
if (ret ~= 0)
then
print(gettext("Press enter"))
else
print(gettext("Press enter when the subprocess has completed"))
end
io.read()
return ret
end
-- attributes are persistent metadata about files (such as execute
-- bit, ACLs, various special flags) which we want to have set and
-- re-set any time the files are modified. the attributes themselves
-- are stored in the roster associated with the revision. each (f,k,v)
-- attribute triple turns into a call to attr_functions[k](f,v) in lua.
if (attr_init_functions == nil) then
attr_init_functions = {}
end
attr_init_functions["mtn:execute"] =
function(filename)
if (is_executable(filename)) then
return "true"
else
return nil
end
end
attr_init_functions["mtn:manual_merge"] =
function(filename)
if (binary_file(filename)) then
return "true" -- binary files must be merged manually
else
return nil
end
end
if (attr_functions == nil) then
attr_functions = {}
end
attr_functions["mtn:execute"] =
function(filename, value)
if (value == "true") then
set_executable(filename)
else
clear_executable(filename)
end
end
function dir_matches(name, dir)
-- helper for ignore_file, matching files within dir, or dir itself.
-- eg for dir of 'CVS', matches CVS/, CVS/*, */CVS/ and */CVS/*
if (string.find(name, "^" .. dir .. "/")) then return true end
if (string.find(name, "^" .. dir .. "$")) then return true end
if (string.find(name, "/" .. dir .. "/")) then return true end
if (string.find(name, "/" .. dir .. "$")) then return true end
return false
end
function portable_readline(f)
line = f:read()
if line ~= nil then
line = string.gsub(line, "\r$","") -- strip possible \r left from windows editing
end
return line
end
function ignore_file(name)
-- project specific
if (ignored_files == nil) then
ignored_files = {}
local ignfile = io.open(".mtn-ignore", "r")
if (ignfile ~= nil) then
local line = portable_readline(ignfile)
while (line ~= nil) do
if line ~= "" then
table.insert(ignored_files, line)
end
line = portable_readline(ignfile)
end
io.close(ignfile)
end
end
local warn_reported_file = false
for i, line in pairs(ignored_files)
do
if (line ~= nil) then
local pcallstatus, result = pcall(function()
return regex.search(line, name)
end)
if pcallstatus == true then
-- no error from the regex.search call
if result == true then return true end
else
-- regex.search had a problem, warn the user their
-- .mtn-ignore file syntax is wrong
if not warn_reported_file then
io.stderr:write("mtn: warning: while matching file '"
.. name .. "':\n")
warn_reported_file = true
end
local prefix = ".mtn-ignore:" .. i .. ": warning: "
io.stderr:write(prefix
.. string.gsub(result, "\n", "\n" .. prefix)
.. "\n\t- skipping this regex for "
.. "all remaining files.\n")
ignored_files[i] = nil
end
end
end
local file_pats = {
-- c/c++
"%.a$", "%.so$", "%.o$", "%.la$", "%.lo$", "^core$",
"/core$", "/core%.%d+$",
-- java
"%.class$",
-- python
"%.pyc$", "%.pyo$",
-- gettext
"%.g?mo$",
-- intltool
"%.intltool%-merge%-cache$",
-- TeX
"%.aux$",
-- backup files
"%.bak$", "%.orig$", "%.rej$", "%~$",
-- vim creates .foo.swp files
"%.[^/]*%.swp$",
-- emacs creates #foo# files
"%#[^/]*%#$",
-- other VCSes (where metadata is stored in named files):
"%.scc$",
-- desktop/directory configuration metadata
"^%.DS_Store$", "/%.DS_Store$", "^desktop%.ini$", "/desktop%.ini$"
}
local dir_pats = {
-- autotools detritus:
"autom4te%.cache", "%.deps", "%.libs",
-- Cons/SCons detritus:
"%.consign", "%.sconsign",
-- other VCSes (where metadata is stored in named dirs):
"CVS", "%.svn", "SCCS", "_darcs", "%.cdv", "%.git", "%.bzr", "%.hg"
}
for _, pat in ipairs(file_pats) do
if string.find(name, pat) then return true end
end
for _, pat in ipairs(dir_pats) do
if dir_matches(name, pat) then return true end
end
return false;
end
-- return true means "binary", false means "text",
-- nil means "unknown, try to guess"
function binary_file(name)
-- some known binaries, return true
local bin_pats = {
"%.gif$", "%.jpe?g$", "%.png$", "%.bz2$", "%.gz$", "%.zip$",
"%.class$", "%.jar$", "%.war$", "%.ear$"
}
-- some known text, return false
local txt_pats = {
"%.cc?$", "%.cxx$", "%.hh?$", "%.hxx$", "%.cpp$", "%.hpp$",
"%.lua$", "%.texi$", "%.sql$", "%.java$"
}
local lowname=string.lower(name)
for _, pat in ipairs(bin_pats) do
if string.find(lowname, pat) then return true end
end
for _, pat in ipairs(txt_pats) do
if string.find(lowname, pat) then return false end
end
-- unknown - read file and use the guess-binary
-- monotone built-in function
return guess_binary_file_contents(name)
end
-- given a file name, return a regular expression which will match
-- lines that name top-level constructs in that file, or "", to disable
-- matching.
function get_encloser_pattern(name)
-- texinfo has special sectioning commands
if (string.find(name, "%.texi$")) then
-- sectioning commands in texinfo: @node, @chapter, @top,
-- @((sub)?sub)?section, @unnumbered(((sub)?sub)?sec)?,
-- @appendix(((sub)?sub)?sec)?, @(|major|chap|sub(sub)?)heading
return ("^@("
.. "node|chapter|top"
.. "|((sub)?sub)?section"
.. "|(unnumbered|appendix)(((sub)?sub)?sec)?"
.. "|(major|chap|sub(sub)?)?heading"
.. ")")
end
-- LaTeX has special sectioning commands. This rule is applied to ordinary
-- .tex files too, since there's no reliable way to distinguish those from
-- latex files anyway, and there's no good pattern we could use for
-- arbitrary plain TeX anyway.
if (string.find(name, "%.tex$")
or string.find(name, "%.ltx$")
or string.find(name, "%.latex$")) then
return ("\\\\("
.. "part|chapter|paragraph|subparagraph"
.. "|((sub)?sub)?section"
.. ")")
end
-- There's no good way to find section headings in raw text, and trying
-- just gives distracting output, so don't even try.
if (string.find(name, "%.txt$")
or string.upper(name) == "README") then
return ""
end
-- This default is correct surprisingly often -- in pretty much any text
-- written with code-like indentation.
return "^[[:alnum:]$_]"
end
function edit_comment(user_log_message)
local exe = nil
-- top priority is VISUAL, then EDITOR, then a series of hardcoded
-- defaults, if available.
local visual = os.getenv("VISUAL")
local editor = os.getenv("EDITOR")
if (visual ~= nil) then exe = visual
elseif (editor ~= nil) then exe = editor
elseif (program_exists_in_path("editor")) then exe = "editor"
elseif (program_exists_in_path("vi")) then exe = "vi"
elseif (string.sub(get_ostype(), 1, 6) ~= "CYGWIN" and
program_exists_in_path("notepad.exe")) then exe = "notepad"
else
io.write(gettext("Could not find editor to enter commit message\n"
.. "Try setting the environment variable EDITOR\n"))
return nil
end
local tmp, tname = temp_file()
if (tmp == nil) then return nil end
tmp:write(user_log_message)
if user_log_message == "" or string.sub(user_log_message, -1) ~= "\n" then
tmp:write("\n")
end
io.close(tmp)
-- By historical convention, VISUAL and EDITOR can contain arguments
-- (and, in fact, arbitrarily complicated shell constructs). Since Lua
-- has no word-splitting functionality, we invoke the shell to deal with
-- anything more complicated than a single word with no metacharacters.
-- This, unfortunately, means we have to quote the file argument.
if (not string.find(exe, "[^%w_.+-]")) then
-- safe to call spawn directly
if (execute(exe, tname) ~= 0) then
io.write(string.format(gettext("Error running editor '%s' "..
"to enter log message\n"),
exe))
os.remove(tname)
return nil
end
else
-- must use shell
local shell = os.getenv("SHELL")
if (shell == nil) then shell = "sh" end
if (not program_exists_in_path(shell)) then
io.write(string.format(gettext("Editor command '%s' needs a shell, "..
"but '%s' is not to be found"),
exe, shell))
os.remove(tname)
return nil
end
-- Single-quoted strings in both Bourne shell and csh can contain
-- anything but a single quote.
local safe_tname = " '" .. string.gsub(tname, "'", "'\\''") .. "'"
if (execute(shell, "-c", editor .. safe_tname) ~= 0) then
io.write(string.format(gettext("Error running editor '%s' "..
"to enter log message\n"),
exe))
os.remove(tname)
return nil
end
end
tmp = io.open(tname, "r")
if (tmp == nil) then os.remove(tname); return nil end
local res = tmp:read("*a")
io.close(tmp)
os.remove(tname)
return res
end
function get_local_key_name(key_identity)
return key_identity.given_name
end
function persist_phrase_ok()
return true
end
function use_inodeprints()
return false
end
function get_date_format_spec(wanted)
-- Return the strftime(3) specification to be used to print dates
-- in human-readable format after conversion to the local timezone.
-- The default uses the preferred date and time representation for
-- the current locale, e.g. the output looks like this: "09/08/2009
-- 06:49:26 PM" for en_US and "date_time_long", or "08.09.2009"
-- for de_DE and "date_short"
--
-- A sampling of other possible formats you might want:
-- default for your locale: "%c" (may include a confusing timezone label)
-- 12 hour format: "%d %b %Y, %I:%M:%S %p"
-- like ctime(3): "%a %b %d %H:%M:%S %Y"
-- email style: "%a, %d %b %Y %H:%M:%S"
-- ISO 8601: "%Y-%m-%d %H:%M:%S" or "%Y-%m-%dT%H:%M:%S"
--
-- ISO 8601, no timezone conversion: ""
--.
if (wanted == "date_long" or wanted == "date_short") then
return "%x"
end
if (wanted == "time_long" or wanted == "time_short") then
return "%X"
end
return "%x %X"
end
-- trust evaluation hooks
function intersection(a,b)
local s={}
local t={}
for k,v in pairs(a) do s[v.name] = 1 end
for k,v in pairs(b) do if s[v] ~= nil then table.insert(t,v) end end
return t
end
function get_revision_cert_trust(signers, id, name, val)
return true
end
function get_manifest_cert_trust(signers, id, name, val)
return true
end
function get_file_cert_trust(signers, id, name, val)
return true
end
function accept_testresult_change(old_results, new_results)
local reqfile = io.open("_MTN/wanted-testresults", "r")
if (reqfile == nil) then return true end
local line = reqfile:read()
local required = {}
while (line ~= nil)
do
required[line] = true
line = reqfile:read()
end
io.close(reqfile)
for test, res in pairs(required)
do
if old_results[test] == true and new_results[test] ~= true
then
return false
end
end
return true
end
-- merger support
-- Fields in the mergers structure:
-- cmd : a function that performs the merge operation using the chosen
-- program, best try.
-- available : a function that checks that the needed program is installed and
-- in $PATH
-- wanted : a function that checks if the user doesn't want to use this
-- method, and returns false if so. This should normally return
-- true, but in some cases, especially when the merger is really
-- an editor, the user might have a preference in EDITOR and we
-- need to respect that.
-- NOTE: wanted is only used when the user has NOT defined the
-- `merger' variable or the MTN_MERGE environment variable.
mergers = {}
-- This merger is designed to fail if there are any conflicts without trying to resolve them
mergers.fail = {
cmd = function (tbl) return false end,
available = function () return true end,
wanted = function () return true end
}
mergers.meld = {
cmd = function (tbl)
io.write(string.format(
"\nWARNING: 'meld' was chosen to perform an external 3-way merge.\n"..
"You must merge all changes to the *CENTER* file.\n\n"
))
local path = "meld"
local ret = execute(path, tbl.lfile, tbl.afile, tbl.rfile)
if (ret ~= 0) then
io.write(string.format(gettext("Error running merger '%s'\n"), path))
return false
end
return tbl.afile
end ,
available = function () return program_exists_in_path("meld") end,
wanted = function () return true end
}
mergers.diffuse = {
cmd = function (tbl)
io.write(string.format(
"\nWARNING: 'diffuse' was chosen to perform an external 3-way merge.\n"..
"You must merge all changes to the *CENTER* file.\n\n"
))
local path = "diffuse"
local ret = execute(path, tbl.lfile, tbl.afile, tbl.rfile)
if (ret ~= 0) then
io.write(string.format(gettext("Error running merger '%s'\n"), path))
return false
end
return tbl.afile
end ,
available = function () return program_exists_in_path("diffuse") end,
wanted = function () return true end
}
mergers.tortoise = {
cmd = function (tbl)
local path = "tortoisemerge"
local ret = execute(path,
string.format("/base:%s", tbl.afile),
string.format("/theirs:%s", tbl.lfile),
string.format("/mine:%s", tbl.rfile),
string.format("/merged:%s", tbl.outfile))
if (ret ~= 0) then
io.write(string.format(gettext("Error running merger '%s'\n"), path))
return false
end
return tbl.outfile
end ,
available = function() return program_exists_in_path ("tortoisemerge") end,
wanted = function () return true end
}
mergers.vim = {
cmd = function (tbl)
function execute_diff3(mine, yours, out)
local diff3_args = {
"diff3",
"--merge",
"--easy-only",
}
table.insert(diff3_args, string.gsub(mine, "\\", "/") .. "")
table.insert(diff3_args, string.gsub(tbl.afile, "\\", "/") .. "")
table.insert(diff3_args, string.gsub(yours, "\\", "/") .. "")
return execute_redirected("", string.gsub(out, "\\", "/"), "", unpack(diff3_args))
end
io.write (string.format("\nWARNING: 'vim' was chosen to perform "..
"an external 3-way merge.\n"..
"You must merge all changes to the "..
"*LEFT* file.\n"))
local vim
if os.getenv ("DISPLAY") ~= nil and program_exists_in_path ("gvim") then
vim = "gvim"
else
vim = "vim"
end
local lfile_merged = tbl.lfile .. ".merged"
local rfile_merged = tbl.rfile .. ".merged"
-- first merge lfile using diff3
local ret = execute_diff3(tbl.lfile, tbl.rfile, lfile_merged)
if ret == 2 then
io.write(string.format(gettext("Error running diff3 for merger '%s'\n"), vim))
os.remove(lfile_merged)
return false
end
-- now merge rfile using diff3
ret = execute_diff3(tbl.rfile, tbl.lfile, rfile_merged)
if ret == 2 then
io.write(string.format(gettext("Error running diff3 for merger '%s'\n"), vim))
os.remove(lfile_merged)
os.remove(rfile_merged)
return false
end
os.rename(lfile_merged, tbl.lfile)
os.rename(rfile_merged, tbl.rfile)
local ret = execute(vim, "-f", "-d", "-c", string.format("silent file %s", tbl.outfile),
tbl.lfile, tbl.rfile)
if (ret ~= 0) then
io.write(string.format(gettext("Error running merger '%s'\n"), vim))
return false
end
return tbl.outfile
end ,
available =
function ()
return program_exists_in_path("diff3") and
(program_exists_in_path("vim") or
program_exists_in_path("gvim"))
end ,
wanted =
function ()
local editor = os.getenv("EDITOR")
if editor and
not (string.find(editor, "vim") or
string.find(editor, "gvim")) then
return false
end
return true
end
}
mergers.rcsmerge = {
cmd = function (tbl)
-- XXX: This is tough - should we check if conflict markers stay or not?
-- If so, we should certainly give the user some way to still force
-- the merge to proceed since they can appear in the files (and I saw
-- that). --pasky
local merge = os.getenv("MTN_RCSMERGE")
if execute(merge, tbl.lfile, tbl.afile, tbl.rfile) == 0 then
copy_text_file(tbl.lfile, tbl.outfile);
return tbl.outfile
end
local ret = execute("vim", "-f", "-c", string.format("file %s", tbl.outfile
),
tbl.lfile)
if (ret ~= 0) then
io.write(string.format(gettext("Error running merger '%s'\n"), "vim"))
return false
end
return tbl.outfile
end,
available =
function ()
local merge = os.getenv("MTN_RCSMERGE")
return merge and
program_exists_in_path(merge) and program_exists_in_path("vim")
end ,
wanted = function () return os.getenv("MTN_RCSMERGE") ~= nil end
}
-- GNU diffutils based merging
mergers.diffutils = {
-- merge procedure execution
cmd = function (tbl)
-- parse options
local option = {}
option.partial = false
option.diff3opts = ""
option.sdiffopts = ""
local options = os.getenv("MTN_MERGE_DIFFUTILS")
if options ~= nil then
for spec in string.gmatch(options, "%s*(%w[^,]*)%s*,?") do
local name, value = string.match(spec, "^(%w+)=([^,]*)")
if name == nil then
name = spec
value = true
end
if type(option[name]) == "nil" then
io.write("mtn: " .. string.format(gettext("invalid \"diffutils\" merger option \"%s\""), name) .. "\n")
return false
end
option[name] = value
end
end
-- determine the diff3(1) command
local diff3 = {
"diff3",
"--merge",
"--label", string.format("%s [left]", tbl.left_path ),
"--label", string.format("%s [ancestor]", tbl.anc_path ),
"--label", string.format("%s [right]", tbl.right_path),
}
if option.diff3opts ~= "" then
for opt in string.gmatch(option.diff3opts, "%s*([^%s]+)%s*") do
table.insert(diff3, opt)
end
end
table.insert(diff3, string.gsub(tbl.lfile, "\\", "/") .. "")
table.insert(diff3, string.gsub(tbl.afile, "\\", "/") .. "")
table.insert(diff3, string.gsub(tbl.rfile, "\\", "/") .. "")
-- dispatch according to major operation mode
if option.partial then
-- partial batch/non-modal 3-way merge "resolution":
-- simply merge content with help of conflict markers
io.write("mtn: " .. gettext("3-way merge via GNU diffutils, resolving conflicts via conflict markers") .. "\n")
local ret = execute_redirected("", string.gsub(tbl.outfile, "\\", "/"), "", unpack(diff3))
if ret == 2 then
io.write("mtn: " .. gettext("error running GNU diffutils 3-way difference/merge tool \"diff3\"") .. "\n")
return false
end
return tbl.outfile
else
-- real interactive/modal 3/2-way merge resolution:
-- display 3-way merge conflict and perform 2-way merge resolution
io.write("mtn: " .. gettext("3-way merge via GNU diffutils, resolving conflicts via interactive prompt") .. "\n")
-- display 3-way merge conflict (batch)
io.write("\n")
io.write("mtn: " .. gettext("---- CONFLICT SUMMARY ------------------------------------------------") .. "\n")
local ret = execute(unpack(diff3))
if ret == 2 then
io.write("mtn: " .. gettext("error running GNU diffutils 3-way difference/merge tool \"diff3\"") .. "\n")
return false
end
-- perform 2-way merge resolution (interactive)
io.write("\n")
io.write("mtn: " .. gettext("---- CONFLICT RESOLUTION ---------------------------------------------") .. "\n")
local sdiff = {
"sdiff",
"--diff-program=diff",
"--suppress-common-lines",
"--minimal",
"--output=" .. string.gsub(tbl.outfile, "\\", "/")
}
if option.sdiffopts ~= "" then
for opt in string.gmatch(option.sdiffopts, "%s*([^%s]+)%s*") do
table.insert(sdiff, opt)
end
end
table.insert(sdiff, string.gsub(tbl.lfile, "\\", "/") .. "")
table.insert(sdiff, string.gsub(tbl.rfile, "\\", "/") .. "")
local ret = execute(unpack(sdiff))
if ret == 2 then
io.write("mtn: " .. gettext("error running GNU diffutils 2-way merging tool \"sdiff\"") .. "\n")
return false
end
return tbl.outfile
end
end,
-- merge procedure availability check
available = function ()
-- make sure the GNU diffutils tools are available
return program_exists_in_path("diff3") and
program_exists_in_path("sdiff") and
program_exists_in_path("diff");
end,
-- merge procedure request check
wanted = function ()
-- assume it is requested (if it is available at all)
return true
end
}
mergers.emacs = {
cmd = function (tbl)
local emacs
if program_exists_in_path("xemacs") then
emacs = "xemacs"
else
emacs = "emacs"
end
local elisp = "(ediff-merge-files-with-ancestor \"%s\" \"%s\" \"%s\" nil \"%s\")"
-- Converting backslashes is necessary on Win32 MinGW; emacs
-- lisp string syntax says '\' is an escape.
local ret = execute(emacs, "--eval",
string.format(elisp,
string.gsub (tbl.lfile, "\\", "/"),
string.gsub (tbl.rfile, "\\", "/"),
string.gsub (tbl.afile, "\\", "/"),
string.gsub (tbl.outfile, "\\", "/")))
if (ret ~= 0) then
io.write(string.format(gettext("Error running merger '%s'\n"), emacs))
return false
end
return tbl.outfile
end,
available =
function ()
return program_exists_in_path("xemacs") or
program_exists_in_path("emacs")
end ,
wanted =
function ()
local editor = os.getenv("EDITOR")
if editor and
not (string.find(editor, "emacs") or
string.find(editor, "gnu")) then
return false
end
return true
end
}
mergers.xxdiff = {
cmd = function (tbl)
local path = "xxdiff"
local ret = execute(path,
"--title1", tbl.left_path,
"--title2", tbl.right_path,
"--title3", tbl.merged_path,
tbl.lfile, tbl.afile, tbl.rfile,
"--merge",
"--merged-filename", tbl.outfile,
"--exit-with-merge-status")
if (ret ~= 0) then
io.write(string.format(gettext("Error running merger '%s'\n"), path))
return false
end
return tbl.outfile
end,
available = function () return program_exists_in_path("xxdiff") end,
wanted = function () return true end
}
mergers.kdiff3 = {
cmd = function (tbl)
local path = "kdiff3"
local ret = execute(path,
"--L1", tbl.anc_path,
"--L2", tbl.left_path,
"--L3", tbl.right_path,
tbl.afile, tbl.lfile, tbl.rfile,
"--merge",
"--o", tbl.outfile)
if (ret ~= 0) then
io.write(string.format(gettext("Error running merger '%s'\n"), path))
return false
end
return tbl.outfile
end,
available = function () return program_exists_in_path("kdiff3") end,
wanted = function () return true end
}
mergers.opendiff = {
cmd = function (tbl)
local path = "opendiff"
-- As opendiff immediately returns, let user confirm manually
local ret = execute_confirm(path,
tbl.lfile,tbl.rfile,
"-ancestor",tbl.afile,
"-merge",tbl.outfile)
if (ret ~= 0) then
io.write(string.format(gettext("Error running merger '%s'\n"), path))
return false
end
return tbl.outfile
end,
available = function () return program_exists_in_path("opendiff") end,
wanted = function () return true end
}
function write_to_temporary_file(data, namehint)
tmp, filename = temp_file(namehint)
if (tmp == nil) then
return nil
end;
tmp:write(data)
io.close(tmp)
return filename
end
function copy_text_file(srcname, destname)
src = io.open(srcname, "r")
if (src == nil) then return nil end
dest = io.open(destname, "w")
if (dest == nil) then return nil end
while true do
local line = src:read()
if line == nil then break end
dest:write(line, "\n")
end
io.close(dest)
io.close(src)
end
function read_contents_of_file(filename, mode)
tmp = io.open(filename, mode)
if (tmp == nil) then
return nil
end
local data = tmp:read("*a")
io.close(tmp)
return data
end
function program_exists_in_path(program)
return existsonpath(program) == 0
end
function get_preferred_merge3_command (tbl)
local default_order = {"diffuse", "kdiff3", "xxdiff", "opendiff",
"tortoise", "emacs", "vim", "meld", "diffutils"}
local function existmerger(name)
local m = mergers[name]
if type(m) == "table" and m.available(tbl) then
return m.cmd
end
return nil
end
local function trymerger(name)
local m = mergers[name]
if type(m) == "table" and m.available(tbl) and m.wanted(tbl) then
return m.cmd
end
return nil
end
-- Check if there's a merger given by the user.
local mkey = os.getenv("MTN_MERGE")
if not mkey then mkey = merger end
if not mkey and os.getenv("MTN_RCSMERGE") then mkey = "rcsmerge" end
-- If there was a user-given merger, see if it exists. If it does, return
-- the cmd function. If not, return nil.
local c
if mkey then c = existmerger(mkey) end
if c then return c,mkey end
if mkey then return nil,mkey end
-- If there wasn't any user-given merger, take the first that's available
-- and wanted.
for _,mkey in ipairs(default_order) do
c = trymerger(mkey) ; if c then return c,mkey end
end
end
function merge3 (anc_path, left_path, right_path, merged_path, ancestor, left, right)
local ret = nil
local tbl = {}
tbl.anc_path = anc_path
tbl.left_path = left_path
tbl.right_path = right_path
tbl.merged_path = merged_path
tbl.afile = nil
tbl.lfile = nil
tbl.rfile = nil
tbl.outfile = nil
tbl.meld_exists = false
tbl.lfile = write_to_temporary_file (left, "left")
tbl.afile = write_to_temporary_file (ancestor, "ancestor")
tbl.rfile = write_to_temporary_file (right, "right")
tbl.outfile = write_to_temporary_file ("", "merged")
if tbl.lfile ~= nil and tbl.rfile ~= nil and tbl.afile ~= nil and tbl.outfile ~= nil
then
local cmd,mkey = get_preferred_merge3_command (tbl)
if cmd ~=nil
then
io.write ("mtn: " .. string.format(gettext("executing external 3-way merge via \"%s\" merger\n"), mkey))
ret = cmd (tbl)
if not ret then
ret = nil
else
ret = read_contents_of_file (ret, "r")
if string.len (ret) == 0
then
ret = nil
end
end
else
if mkey then
io.write (string.format("The possible commands for the "..mkey.." merger aren't available.\n"..
"You may want to check that $MTN_MERGE or the lua variable `merger' is set\n"..
"to something available. If you want to use vim or emacs, you can also\n"..
"set $EDITOR to something appropriate.\n"))
else
io.write (string.format("No external 3-way merge command found.\n"..
"You may want to check that $EDITOR is set to an editor that supports 3-way\n"..
"merge, set this explicitly in your get_preferred_merge3_command hook,\n"..
"or add a 3-way merge program to your path.\n"))
end
end
end
os.remove (tbl.lfile)
os.remove (tbl.rfile)
os.remove (tbl.afile)
os.remove (tbl.outfile)
return ret
end
-- expansion of values used in selector completion
function expand_selector(str)
-- something which looks like a generic cert pattern
if string.find(str, "^[^=]*=.*$")
then
return ("c:" .. str)
end
-- something which looks like an email address
if string.find(str, "[%w%-_]+@[%w%-_]+")
then
return ("a:" .. str)
end
-- something which looks like a branch name
if string.find(str, "[%w%-]+%.[%w%-]+")
then
return ("b:" .. str)
end
-- a sequence of nothing but hex digits
if string.find(str, "^%x+$")
then
return ("i:" .. str)
end
-- tries to expand as a date
local dtstr = expand_date(str)
if dtstr ~= nil
then
return ("d:" .. dtstr)
end
return nil
end
-- expansion of a date expression
function expand_date(str)
-- simple date patterns
if string.find(str, "^19%d%d%-%d%d")
or string.find(str, "^20%d%d%-%d%d")
then
return (str)
end
-- "now"
if str == "now"
then
local t = os.time(os.date('!*t'))
return os.date("%Y-%m-%dT%H:%M:%S", t)
end
-- today don't uses the time # for xgettext's sake, an extra quote
if str == "today"
then
local t = os.time(os.date('!*t'))
return os.date("%Y-%m-%d", t)
end
-- "yesterday", the source of all hangovers
if str == "yesterday"
then
local t = os.time(os.date('!*t'))
return os.date("%Y-%m-%d", t - 86400)
end
-- "CVS style" relative dates such as "3 weeks ago"
local trans = {
minute = 60;
hour = 3600;
day = 86400;
week = 604800;
month = 2678400;
year = 31536000
}
local pos, len, n, type = string.find(str, "(%d+) ([minutehordaywk]+)s? ago")
if trans[type] ~= nil
then
local t = os.time(os.date('!*t'))
if trans[type] <= 3600
then
return os.date("%Y-%m-%dT%H:%M:%S", t - (n * trans[type]))
else
return os.date("%Y-%m-%d", t - (n * trans[type]))
end
end
return nil
end
external_diff_default_args = "-u"
-- default external diff, works for gnu diff
function external_diff(file_path, data_old, data_new, is_binary, diff_args, rev_old, rev_new)
local old_file = write_to_temporary_file(data_old);
local new_file = write_to_temporary_file(data_new);
if diff_args == nil then diff_args = external_diff_default_args end
execute("diff", diff_args, "--label", file_path .. "\told", old_file, "--label", file_path .. "\tnew", new_file);
os.remove (old_file);
os.remove (new_file);
end
-- netsync permissions hooks (and helper)
function globish_match(glob, str)
local pcallstatus, result = pcall(function() if (globish.match(glob, str)) then return true else return false end end)
if pcallstatus == true then
-- no error
return result
else
-- globish.match had a problem
return nil
end
end
function _get_netsync_read_permitted(branch, ident, permfilename, state)
if not exists(permfilename) or isdir(permfilename) then
return false
end
local permfile = io.open(permfilename, "r")
if (permfile == nil) then return false end
local dat = permfile:read("*a")
io.close(permfile)
local res = parse_basic_io(dat)
if res == nil then
io.stderr:write("file "..permfilename.." cannot be parsed\n")
return false,"continue"
end
state["matches"] = state["matches"] or false
state["cont"] = state["cont"] or false
for i, item in pairs(res)
do
-- legal names: pattern, allow, deny, continue
if item.name == "pattern" then
if state["matches"] and not state["cont"] then return false end
state["matches"] = false
state["cont"] = false
for j, val in pairs(item.values) do
if globish_match(val, branch) then state["matches"] = true end
end
elseif item.name == "allow" then if state["matches"] then
for j, val in pairs(item.values) do
if val == "*" then return true end
if val == "" and ident == nil then return true end
if ident ~= nil and val == ident.id then return true end
if ident ~= nil and globish_match(val, ident.name) then return true end
end
end elseif item.name == "deny" then if state["matches"] then
for j, val in pairs(item.values) do
if val == "*" then return false end
if val == "" and ident == nil then return false end
if ident ~= nil and val == ident.id then return false end
if ident ~= nil and globish_match(val, ident.name) then return false end
end
end elseif item.name == "continue" then if state["matches"] then
state["cont"] = true
for j, val in pairs(item.values) do
if val == "false" or val == "no" then
state["cont"] = false
end
end
end elseif item.name ~= "comment" then
io.stderr:write("unknown symbol in read-permissions: " .. item.name .. "\n")
return false
end
end
return false
end
function get_netsync_read_permitted(branch, ident)
local permfilename = get_confdir() .. "/read-permissions"
local permdirname = permfilename .. ".d"
local state = {}
if _get_netsync_read_permitted(branch, ident, permfilename, state) then
return true
end
if isdir(permdirname) then
local files = read_directory(permdirname)
table.sort(files)
for _,f in ipairs(files) do
pf = permdirname.."/"..f
if _get_netsync_read_permitted(branch, ident, pf, state) then
return true
end
end
end
return false
end
function _get_netsync_write_permitted(ident, permfilename)
if not exists(permfilename) or isdir(permfilename) then return false end
local permfile = io.open(permfilename, "r")
if (permfile == nil) then
return false
end
local matches = false
local line = permfile:read()
while (not matches and line ~= nil) do
local _, _, ln = string.find(line, "%s*([^%s]*)%s*")
if ln == "*" then matches = true end
if ln == ident.id then matches = true end
if globish_match(ln, ident.name) then matches = true end
line = permfile:read()
end
io.close(permfile)
return matches
end
function get_netsync_write_permitted(ident)
local permfilename = get_confdir() .. "/write-permissions"
local permdirname = permfilename .. ".d"
if _get_netsync_write_permitted(ident, permfilename) then return true end
if isdir(permdirname) then
local files = read_directory(permdirname)
table.sort(files)
for _,f in ipairs(files) do
pf = permdirname.."/"..f
if _get_netsync_write_permitted(ident, pf) then return true end
end
end
return false
end
-- This is a simple function which assumes you're going to be spawning
-- a copy of mtn, so reuses a common bit at the end for converting
-- local args into remote args. You might need to massage the logic a
-- bit if this doesn't fit your assumptions.
function get_netsync_connect_command(uri, args)
local argv = nil
if uri["scheme"] == "ssh"
and uri["host"]
and uri["path"] then
argv = { "ssh" }
if uri["user"] then
table.insert(argv, "-l")
table.insert(argv, uri["user"])
end
if uri["port"] then
table.insert(argv, "-p")
table.insert(argv, uri["port"])
end
-- ssh://host/~/dir/file.mtn or
-- ssh://host/~user/dir/file.mtn should be home-relative
if string.find(uri["path"], "^/~") then
uri["path"] = string.sub(uri["path"], 2)
end
table.insert(argv, uri["host"])
end
if uri["scheme"] == "file" and uri["path"] then
argv = { }
end
if uri["scheme"] == "ssh+ux"
and uri["host"]
and uri["path"] then
argv = { "ssh" }
if uri["user"] then
table.insert(argv, "-l")
table.insert(argv, uri["user"])
end
if uri["port"] then
table.insert(argv, "-p")
table.insert(argv, uri["port"])
end
-- ssh://host/~/dir/file.mtn or
-- ssh://host/~user/dir/file.mtn should be home-relative
if string.find(uri["path"], "^/~") then
uri["path"] = string.sub(uri["path"], 2)
end
table.insert(argv, uri["host"])
table.insert(argv, get_remote_unix_socket_command(uri["host"]))
table.insert(argv, "-")
table.insert(argv, "UNIX-CONNECT:" .. uri["path"])
else
if argv then
-- start remote monotone process
table.insert(argv, get_mtn_command(uri["host"]))
if args["debug"] then
table.insert(argv, "--verbose")
else
table.insert(argv, "--quiet")
end
table.insert(argv, "--db")
table.insert(argv, uri["path"])
table.insert(argv, "serve")
table.insert(argv, "--stdio")
table.insert(argv, "--no-transport-auth")
-- else scheme does not require starting a new remote
-- process (ie mtn:)
end
end
return argv
end
function use_transport_auth(uri)
if uri["scheme"] == "ssh"
or uri["scheme"] == "ssh+ux"
or uri["scheme"] == "file" then
return false
else
return true
end
end
function get_mtn_command(host)
return "mtn"
end
function get_remote_unix_socket_command(host)
return "socat"
end
function get_default_command_options(command)
local default_args = {}
return default_args
end
function get_default_database_alias()
return ":default.mtn"
end
function get_default_database_locations()
local paths = {}
table.insert(paths, get_confdir() .. "/databases")
return paths
end
function get_default_database_glob()
return "*.{mtn,db}"
end
hook_wrapper_dump = {}
hook_wrapper_dump.depth = 0
hook_wrapper_dump._string = function(s) return string.format("%q", s) end
hook_wrapper_dump._number = function(n) return tostring(n) end
hook_wrapper_dump._boolean = function(b) if (b) then return "true" end return "false" end
hook_wrapper_dump._userdata = function(u) return "nil --[[userdata]]" end
-- if we really need to return / serialize functions we could do it
-- like cbreak@irc.freenode.net did here: http://lua-users.org/wiki/TablePersistence
hook_wrapper_dump._function = function(f) return "nil --[[function]]" end
hook_wrapper_dump._nil = function(n) return "nil" end
hook_wrapper_dump._thread = function(t) return "nil --[[thread]]" end
hook_wrapper_dump._lightuserdata = function(l) return "nil --[[lightuserdata]]" end
hook_wrapper_dump._table = function(t)
local buf = ''
if (hook_wrapper_dump.depth > 0) then
buf = buf .. '{\n'
end
hook_wrapper_dump.depth = hook_wrapper_dump.depth + 1;
for k,v in pairs(t) do
buf = buf..string.format('%s[%s] = %s;\n',
string.rep("\t", hook_wrapper_dump.depth - 1),
hook_wrapper_dump["_" .. type(k)](k),
hook_wrapper_dump["_" .. type(v)](v))
end
hook_wrapper_dump.depth = hook_wrapper_dump.depth - 1;
if (hook_wrapper_dump.depth > 0) then
buf = buf .. string.rep("\t", hook_wrapper_dump.depth - 1) .. '}'
end
return buf
end
function hook_wrapper(func_name, ...)
-- we have to ensure that nil arguments are restored properly for the
-- function call, see http://lua-users.org/wiki/StoringNilsInTables
local args = { n=select('#', ...), ... }
for i=1,args.n do
local val = assert(loadstring("return " .. args[i]),
"argument "..args[i].." could not be evaluated")()
assert(val ~= nil or args[i] == "nil",
"argument "..args[i].." was evaluated to nil")
args[i] = val
end
local res = { _G[func_name](unpack(args, 1, args.n)) }
return hook_wrapper_dump._table(res)
end
do
-- Hook functions are tables containing any of the following 6 items
-- with associated functions:
--
-- startup Corresponds to note_mtn_startup()
-- start Corresponds to note_netsync_start()
-- revision_received Corresponds to note_netsync_revision_received()
-- revision_sent Corresponds to note_netsync_revision_sent()
-- cert_received Corresponds to note_netsync_cert_received()
-- cert_sent Corresponds to note_netsync_cert_sent()
-- pubkey_received Corresponds to note_netsync_pubkey_received()
-- pubkey_sent Corresponds to note_netsync_pubkey_sent()
-- end Corresponds to note_netsync_end()
--
-- Those functions take exactly the same arguments as the corresponding
-- global functions, but return a different kind of value, a tuple
-- composed of a return code and a value to be returned back to monotone.
-- The codes are strings:
-- "continue" and "stop"
-- When the code "continue" is returned and there's another notifier, the
-- second value is ignored and the next notifier is called. Otherwise,
-- the second value is returned immediately.
local hook_functions = {}
local supported_items = {
"startup",
"start", "revision_received", "revision_sent", "cert_received", "cert_sent",
"pubkey_received", "pubkey_sent", "end"
}
function _hook_functions_helper(f,...)
local s = "continue"
local v = nil
for _,n in pairs(hook_functions) do
if n[f] then
s,v = n[f](...)
end
if s ~= "continue" then
break
end
end
return v
end
function note_mtn_startup(...)
return _hook_functions_helper("startup",...)
end
function note_netsync_start(...)
return _hook_functions_helper("start",...)
end
function note_netsync_revision_received(...)
return _hook_functions_helper("revision_received",...)
end
function note_netsync_revision_sent(...)
return _hook_functions_helper("revision_sent",...)
end
function note_netsync_cert_received(...)
return _hook_functions_helper("cert_received",...)
end
function note_netsync_cert_sent(...)
return _hook_functions_helper("cert_sent",...)
end
function note_netsync_pubkey_received(...)
return _hook_functions_helper("pubkey_received",...)
end
function note_netsync_pubkey_sent(...)
return _hook_functions_helper("pubkey_sent",...)
end
function note_netsync_end(...)
return _hook_functions_helper("end",...)
end
function add_hook_functions(functions, precedence)
if type(functions) ~= "table" or type(precedence) ~= "number" then
return false, "Invalid type"
end
if hook_functions[precedence] then
return false, "Precedence already taken"
end
local unknown_items = ""
local warning = nil
local is_member =
function (s,t)
for k,v in pairs(t) do if s == v then return true end end
return false
end
for n,f in pairs(functions) do
if type(n) == "string" then
if not is_member(n, supported_items) then
if unknown_items ~= "" then
unknown_items = unknown_items .. ","
end
unknown_items = unknown_items .. n
end
if type(f) ~= "function" then
return false, "Value for functions item "..n.." isn't a function"
end
else
warning = "Non-string item keys found in functions table"
end
end
if warning == nil and unknown_items ~= "" then
warning = "Unknown item(s) " .. unknown_items .. " in functions table"
end
hook_functions[precedence] = functions
return true, warning
end
function push_hook_functions(functions)
local n = table.maxn(hook_functions) + 1
return add_hook_functions(functions, n)
end
-- Kept for backward compatibility
function add_netsync_notifier(notifier, precedence)
return add_hook_functions(notifier, precedence)
end
function push_netsync_notifier(notifier)
return push_hook_functions(notifier)
end
end
-- to ensure only mapped authors are allowed through
-- return "" from unmapped_git_author
-- and validate_git_author will fail
function unmapped_git_author(author)
-- replace "foo@bar" with "foo <foo@bar>"
name = author:match("^([^<>]+)@[^<>]+$")
if name then
return name .. " <" .. author .. ">"
end
-- replace "<foo@bar>" with "foo <foo@bar>"
name = author:match("^<([^<>]+)@[^<>]+>$")
if name then
return name .. " " .. author
end
-- replace "foo" with "foo <foo>"
name = author:match("^[^<>@]+$")
if name then
return name .. " <" .. name .. ">"
end
return author -- unchanged
end
function validate_git_author(author)
-- ensure author matches the "Name <email>" format git expects
if author:match("^[^<]+ <[^>]*>$") then
return true
end
return false
end
function get_man_page_formatter_command()
local term_width = guess_terminal_width() - 2
-- The string returned is run in a process created with 'popen'
-- (see cmd.cc manpage).
--
-- On Unix (and POSIX compliant systems), 'popen' runs 'sh' with
-- the inherited path.
--
-- On MinGW, 'popen' runs 'cmd.exe' with the inherited path. MinGW
-- does not (currently) provide nroff or equivalent. So we assume
-- sh, nroff, locale and less are also installed, from Cygwin or
-- some other toolset.
--
-- GROFF_ENCODING is an environment variable that, when set, tells
-- groff (called by nroff where applicable) to use preconv to convert
-- the input from the given encoding to something groff understands.
-- For example, groff doesn NOT understand raw UTF-8 as input, but
-- it does understand unicode, which preconv will happily provide.
-- This doesn't help people that don't use groff, unfortunately.
-- Patches are welcome!
if string.sub(get_ostype(), 1, 7) == "Windows" then
return string.format("sh -c 'GROFF_ENCODING=`locale charmap` nroff -man -rLL=%dn' | less -R", term_width)
else
return string.format("GROFF_ENCODING=`locale charmap` nroff -man -rLL=%dn | less -R", term_width)
end
end
</pre>
</body></html>
|