Many hyperlinks are disabled.
Use anonymous login
to enable hyperlinks.
Overview
| Comment: | Add an 'artifact' command to TH1. |
|---|---|
| Downloads: | Tarball | ZIP archive |
| Timelines: | family | ancestors | descendants | both | trunk |
| Files: | files | file ages | folders |
| SHA1: |
109d8f5dd67e0de0f50e3bd73f4022cc |
| User & Date: | mistachkin 2014-06-16 18:02:01.321 |
Context
|
2014-06-16
| ||
| 18:34 | Fix memory leak in the new TH1 artifact command. ... (check-in: 74099a5c8d user: mistachkin tags: trunk) | |
| 18:02 | Add an 'artifact' command to TH1. ... (check-in: 109d8f5dd6 user: mistachkin tags: trunk) | |
| 16:44 | Update the list of changes. ... (check-in: 42dec3fedd user: mistachkin tags: trunk) | |
Changes
Changes to src/th_main.c.
| ︙ | ︙ | |||
137 138 139 140 141 142 143 |
fossil_print("\n------------------ BEGIN TRACE LOG ------------------\n");
fossil_print("%s", blob_str(&g.thLog));
fossil_print("\n------------------- END TRACE LOG -------------------\n");
}
}
/*
| | | 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 |
fossil_print("\n------------------ BEGIN TRACE LOG ------------------\n");
fossil_print("%s", blob_str(&g.thLog));
fossil_print("\n------------------- END TRACE LOG -------------------\n");
}
}
/*
** TH1 command: httpize STRING
**
** Escape all characters of STRING which have special meaning in URI
** components. Return a new string result.
*/
static int httpizeCmd(
Th_Interp *interp,
void *p,
|
| ︙ | ︙ | |||
165 166 167 168 169 170 171 | /* ** True if output is enabled. False if disabled. */ static int enableOutput = 1; /* | | | 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 | /* ** True if output is enabled. False if disabled. */ static int enableOutput = 1; /* ** TH1 command: enable_output BOOLEAN ** ** Enable or disable the puts and hputs commands. */ static int enableOutputCmd( Th_Interp *interp, void *p, int argc, |
| ︙ | ︙ | |||
240 241 242 243 244 245 246 |
sendText("ERROR: ", -1, 0);
sendText((char*)z, n, 1);
sendText(forceCgi || g.cgiOutput ? "</p>" : "\n", -1, 0);
enableOutput = savedEnable;
}
/*
| | | | | 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 |
sendText("ERROR: ", -1, 0);
sendText((char*)z, n, 1);
sendText(forceCgi || g.cgiOutput ? "</p>" : "\n", -1, 0);
enableOutput = savedEnable;
}
/*
** TH1 command: puts STRING
** TH1 command: html STRING
**
** Output STRING escaped for HTML (html) or unchanged (puts).
*/
static int putsCmd(
Th_Interp *interp,
void *pConvert,
int argc,
const char **argv,
int *argl
){
if( argc!=2 ){
return Th_WrongNumArgs(interp, "puts STRING");
}
sendText((char*)argv[1], argl[1], *(unsigned int*)pConvert);
return TH_OK;
}
/*
** TH1 command: wiki STRING
**
** Render the input string as wiki.
*/
static int wikiCmd(
Th_Interp *interp,
void *p,
int argc,
|
| ︙ | ︙ | |||
285 286 287 288 289 290 291 |
wiki_convert(&src, 0, flags);
blob_reset(&src);
}
return TH_OK;
}
/*
| | | 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 |
wiki_convert(&src, 0, flags);
blob_reset(&src);
}
return TH_OK;
}
/*
** TH1 command: htmlize STRING
**
** Escape all characters of STRING which have special meaning in HTML.
** Return a new string result.
*/
static int htmlizeCmd(
Th_Interp *interp,
void *p,
|
| ︙ | ︙ | |||
308 309 310 311 312 313 314 | zOut = htmlize((char*)argv[1], argl[1]); Th_SetResult(interp, zOut, -1); free(zOut); return TH_OK; } /* | | | 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 | zOut = htmlize((char*)argv[1], argl[1]); Th_SetResult(interp, zOut, -1); free(zOut); return TH_OK; } /* ** TH1 command: date ** ** Return a string which is the current time and date. If the ** -local option is used, the date appears using localtime instead ** of UTC. */ static int dateCmd( Th_Interp *interp, |
| ︙ | ︙ | |||
333 334 335 336 337 338 339 | } Th_SetResult(interp, zOut, -1); free(zOut); return TH_OK; } /* | | | 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 | } Th_SetResult(interp, zOut, -1); free(zOut); return TH_OK; } /* ** TH1 command: hascap STRING... ** ** Return true if the user has all of the capabilities listed in STRING. */ static int hascapCmd( Th_Interp *interp, void *p, int argc, |
| ︙ | ︙ | |||
359 360 361 362 363 364 365 |
Th_Trace("[hascap %#h] => %d<br />\n", argl[1], argv[1], rc);
}
Th_SetResultInt(interp, rc);
return TH_OK;
}
/*
| | | 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 |
Th_Trace("[hascap %#h] => %d<br />\n", argl[1], argv[1], rc);
}
Th_SetResultInt(interp, rc);
return TH_OK;
}
/*
** TH1 command: hasfeature STRING
**
** Return true if the fossil binary has the given compile-time feature
** enabled. The set of features includes:
**
** "ssl" = FOSSIL_ENABLE_SSL
** "th1Hooks" = FOSSIL_ENABLE_TH1_HOOKS
** "tcl" = FOSSIL_ENABLE_TCL
|
| ︙ | ︙ | |||
437 438 439 440 441 442 443 | } Th_SetResultInt(interp, rc); return TH_OK; } /* | | | 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 | } Th_SetResultInt(interp, rc); return TH_OK; } /* ** TH1 command: tclReady ** ** Return true if the fossil binary has the Tcl integration feature ** enabled and it is currently available for use by TH1 scripts. ** */ static int tclReadyCmd( Th_Interp *interp, |
| ︙ | ︙ | |||
468 469 470 471 472 473 474 | } Th_SetResultInt(interp, rc); return TH_OK; } /* | | | 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 | } Th_SetResultInt(interp, rc); return TH_OK; } /* ** TH1 command: anycap STRING ** ** Return true if the user has any one of the capabilities listed in STRING. */ static int anycapCmd( Th_Interp *interp, void *p, int argc, |
| ︙ | ︙ | |||
495 496 497 498 499 500 501 |
Th_Trace("[hascap %#h] => %d<br />\n", argl[1], argv[1], rc);
}
Th_SetResultInt(interp, rc);
return TH_OK;
}
/*
| | | 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 |
Th_Trace("[hascap %#h] => %d<br />\n", argl[1], argv[1], rc);
}
Th_SetResultInt(interp, rc);
return TH_OK;
}
/*
** TH1 command: combobox NAME TEXT-LIST NUMLINES
**
** Generate an HTML combobox. NAME is both the name of the
** CGI parameter and the name of a variable that contains the
** currently selected value. TEXT-LIST is a list of possible
** values for the combobox. NUMLINES is 1 for a true combobox.
** If NUMLINES is greater than one then the display is a listbox
** with the number of lines given.
|
| ︙ | ︙ | |||
555 556 557 558 559 560 561 |
sendText("</select>", -1, 0);
Th_Free(interp, azElem);
}
return TH_OK;
}
/*
| | | 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 |
sendText("</select>", -1, 0);
Th_Free(interp, azElem);
}
return TH_OK;
}
/*
** TH1 command: linecount STRING MAX MIN
**
** Return one more than the number of \n characters in STRING. But
** never return less than MIN or more than MAX.
*/
static int linecntCmd(
Th_Interp *interp,
void *p,
|
| ︙ | ︙ | |||
590 591 592 593 594 595 596 | if( n<iMin ) n = iMin; if( n>iMax ) n = iMax; Th_SetResultInt(interp, n); return TH_OK; } /* | | | 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 | if( n<iMin ) n = iMin; if( n>iMax ) n = iMax; Th_SetResultInt(interp, n); return TH_OK; } /* ** TH1 command: repository ?BOOLEAN? ** ** Return the fully qualified file name of the open repository or an empty ** string if one is not currently open. Optionally, it will attempt to open ** the repository if the boolean argument is non-zero. */ static int repositoryCmd( Th_Interp *interp, |
| ︙ | ︙ | |||
618 619 620 621 622 623 624 |
if( openRepository ) db_find_and_open_repository(OPEN_OK_NOT_FOUND, 0);
}
Th_SetResult(interp, g.zRepositoryName, -1);
return TH_OK;
}
/*
| | | 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 |
if( openRepository ) db_find_and_open_repository(OPEN_OK_NOT_FOUND, 0);
}
Th_SetResult(interp, g.zRepositoryName, -1);
return TH_OK;
}
/*
** TH1 command: checkout ?BOOLEAN?
**
** Return the fully qualified directory name of the current checkout or an
** empty string if it is not available. Optionally, it will attempt to find
** the current checkout, opening the configuration ("user") database and the
** repository as necessary, if the boolean argument is non-zero.
*/
static int checkoutCmd(
|
| ︙ | ︙ | |||
647 648 649 650 651 652 653 |
if( openCheckout ) db_open_local(0);
}
Th_SetResult(interp, g.zLocalRoot, -1);
return TH_OK;
}
/*
| | | 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 |
if( openCheckout ) db_open_local(0);
}
Th_SetResult(interp, g.zLocalRoot, -1);
return TH_OK;
}
/*
** TH1 command: trace STRING
**
** Generate a TH1 trace message if debugging is enabled.
*/
static int traceCmd(
Th_Interp *interp,
void *p,
int argc,
|
| ︙ | ︙ | |||
669 670 671 672 673 674 675 |
Th_Trace("%s", argv[1]);
}
Th_SetResult(interp, 0, 0);
return TH_OK;
}
/*
| | | 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 |
Th_Trace("%s", argv[1]);
}
Th_SetResult(interp, 0, 0);
return TH_OK;
}
/*
** TH1 command: getParameter NAME ?DEFAULT?
**
** Return the value of the specified query parameter or the specified default
** value when there is no matching query parameter.
*/
static int getParameterCmd(
Th_Interp *interp,
void *p,
|
| ︙ | ︙ | |||
693 694 695 696 697 698 699 |
zDefault = argv[2];
}
Th_SetResult(interp, cgi_parameter(argv[1], zDefault), -1);
return TH_OK;
}
/*
| | | | | 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 |
zDefault = argv[2];
}
Th_SetResult(interp, cgi_parameter(argv[1], zDefault), -1);
return TH_OK;
}
/*
** TH1 command: setParameter NAME VALUE
**
** Sets the value of the specified query parameter.
*/
static int setParameterCmd(
Th_Interp *interp,
void *p,
int argc,
const char **argv,
int *argl
){
if( argc!=3 ){
return Th_WrongNumArgs(interp, "setParameter NAME VALUE");
}
cgi_replace_parameter(mprintf("%s", argv[1]), mprintf("%s", argv[2]));
return TH_OK;
}
/*
** TH1 command: render STRING
**
** Renders the template and writes the results.
*/
static int renderCmd(
Th_Interp *interp,
void *p,
int argc,
const char **argv,
int *argl
){
int rc;
if( argc!=2 ){
return Th_WrongNumArgs(interp, "render STRING");
}
rc = Th_Render(argv[1]);
Th_SetResult(interp, 0, 0);
return rc;
}
/*
** TH1 command: styleHeader TITLE
**
** Render the configured style header.
*/
static int styleHeaderCmd(
Th_Interp *interp,
void *p,
int argc,
|
| ︙ | ︙ | |||
758 759 760 761 762 763 764 |
}else{
Th_SetResult(interp, "repository unavailable", -1);
return TH_ERROR;
}
}
/*
| | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 |
}else{
Th_SetResult(interp, "repository unavailable", -1);
return TH_ERROR;
}
}
/*
** TH1 command: styleFooter
**
** Render the configured style footer.
*/
static int styleFooterCmd(
Th_Interp *interp,
void *p,
int argc,
const char **argv,
int *argl
){
if( argc!=1 ){
return Th_WrongNumArgs(interp, "styleFooter");
}
if( Th_IsRepositoryOpen() ){
style_footer();
Th_SetResult(interp, 0, 0);
return TH_OK;
}else{
Th_SetResult(interp, "repository unavailable", -1);
return TH_ERROR;
}
}
/*
** TH1 command: artifact ID
**
** Attempts to locate the specified artifact and return its contents. An
** error is generated if the repository is not open or the artifact cannot
** be found.
*/
static int artifactCmd(
Th_Interp *interp,
void *p,
int argc,
const char **argv,
int *argl
){
if( argc!=2 ){
return Th_WrongNumArgs(interp, "artifact ID");
}
if( Th_IsRepositoryOpen() ){
int rid;
Blob content;
rid = name_to_rid(argv[1]);
if( rid!=0 && content_get(rid, &content) ){
Th_SetResult(interp, blob_str(&content), blob_size(&content));
return TH_OK;
}else{
Th_SetResult(interp, "artifact not found", -1);
return TH_ERROR;
}
}else{
Th_SetResult(interp, "repository unavailable", -1);
return TH_ERROR;
}
}
#ifdef _WIN32
|
| ︙ | ︙ | |||
821 822 823 824 825 826 827 |
*piKernel =
((sqlite3_uint64)s.ru_stime.tv_sec)*1000000 + s.ru_stime.tv_usec;
}
#endif
}
/*
| | | | 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 |
*piKernel =
((sqlite3_uint64)s.ru_stime.tv_sec)*1000000 + s.ru_stime.tv_usec;
}
#endif
}
/*
** TH1 command: utime
**
** Return the number of microseconds of CPU time consumed by the current
** process in user space.
*/
static int utimeCmd(
Th_Interp *interp,
void *p,
int argc,
const char **argv,
int *argl
){
sqlite3_uint64 x;
char zUTime[50];
getCpuTimes(&x, 0);
sqlite3_snprintf(sizeof(zUTime), zUTime, "%llu", x);
Th_SetResult(interp, zUTime, -1);
return TH_OK;
}
/*
** TH1 command: stime
**
** Return the number of microseconds of CPU time consumed by the current
** process in system space.
*/
static int stimeCmd(
Th_Interp *interp,
void *p,
|
| ︙ | ︙ | |||
864 865 866 867 868 869 870 | sqlite3_snprintf(sizeof(zUTime), zUTime, "%llu", x); Th_SetResult(interp, zUTime, -1); return TH_OK; } /* | | | 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 | sqlite3_snprintf(sizeof(zUTime), zUTime, "%llu", x); Th_SetResult(interp, zUTime, -1); return TH_OK; } /* ** TH1 command: randhex N ** ** Return N*2 random hexadecimal digits with N<50. If N is omitted, ** use a value of 10. */ static int randhexCmd( Th_Interp *interp, void *p, |
| ︙ | ︙ | |||
898 899 900 901 902 903 904 | sqlite3_randomness(n, aRand); encode16(aRand, zOut, n); Th_SetResult(interp, (const char *)zOut, -1); return TH_OK; } /* | | | 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 | sqlite3_randomness(n, aRand); encode16(aRand, zOut, n); Th_SetResult(interp, (const char *)zOut, -1); return TH_OK; } /* ** TH1 command: query SQL CODE ** ** Run the SQL query given by the SQL argument. For each row in the result ** set, run CODE. ** ** In SQL, parameters such as $var are filled in using the value of variable ** "var". Result values are stored in variables with the column name prior ** to each invocation of CODE. |
| ︙ | ︙ | |||
980 981 982 983 984 985 986 |
return TH_ERROR;
}
}
return res;
}
/*
| | | 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 |
return TH_ERROR;
}
}
return res;
}
/*
** TH1 command: setting name
**
** Gets and returns the value of the specified Fossil setting.
*/
#define SETTING_WRONGNUMARGS "setting ?-strict? ?--? name"
static int settingCmd(
Th_Interp *interp,
void *p,
|
| ︙ | ︙ | |||
1025 1026 1027 1028 1029 1030 1031 |
Th_Trace("[setting %s%#h] => %d<br />\n", strict ? "strict " : "",
argl[nArg], argv[nArg], rc);
}
return rc;
}
/*
| | | 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 |
Th_Trace("[setting %s%#h] => %d<br />\n", strict ? "strict " : "",
argl[nArg], argv[nArg], rc);
}
return rc;
}
/*
** TH1 command: regexp ?-nocase? ?--? exp string
**
** Checks the string against the specified regular expression and returns
** non-zero if it matches. If the regular expression is invalid or cannot
** be compiled, an error will be generated.
*/
#define REGEXP_WRONGNUMARGS "regexp ?-nocase? ?--? exp string"
static int regexpCmd(
|
| ︙ | ︙ | |||
1068 1069 1070 1071 1072 1073 1074 |
rc = TH_ERROR;
}
re_free(pRe);
return rc;
}
/*
| | | 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 |
rc = TH_ERROR;
}
re_free(pRe);
return rc;
}
/*
** TH1 command: http ?-asynchronous? ?--? url ?payload?
**
** Perform an HTTP or HTTPS request for the specified URL. If a
** payload is present, it will be interpreted as text/plain and
** the POST method will be used; otherwise, the GET method will
** be used. Upon success, if the -asynchronous option is used, an
** empty string is returned as the result; otherwise, the response
** from the server is returned as the result. Synchronous requests
|
| ︙ | ︙ | |||
1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 |
static unsigned int aFlags[] = { 0, 1, WIKI_LINKSONLY };
static struct _Command {
const char *zName;
Th_CommandProc xProc;
void *pContext;
} aCommand[] = {
{"anycap", anycapCmd, 0},
{"checkout", checkoutCmd, 0},
{"combobox", comboboxCmd, 0},
{"date", dateCmd, 0},
{"decorate", wikiCmd, (void*)&aFlags[2]},
{"enable_output", enableOutputCmd, 0},
{"getParameter", getParameterCmd, 0},
{"httpize", httpizeCmd, 0},
| > | 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 |
static unsigned int aFlags[] = { 0, 1, WIKI_LINKSONLY };
static struct _Command {
const char *zName;
Th_CommandProc xProc;
void *pContext;
} aCommand[] = {
{"anycap", anycapCmd, 0},
{"artifact", artifactCmd, 0},
{"checkout", checkoutCmd, 0},
{"combobox", comboboxCmd, 0},
{"date", dateCmd, 0},
{"decorate", wikiCmd, (void*)&aFlags[2]},
{"enable_output", enableOutputCmd, 0},
{"getParameter", getParameterCmd, 0},
{"httpize", httpizeCmd, 0},
|
| ︙ | ︙ |
Changes to src/tkt.c.
| ︙ | ︙ | |||
472 473 474 475 476 477 478 |
attachment_list(zFullName, "<hr /><h2>Attachments:</h2><ul>");
}
style_footer();
}
/*
| | | 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 |
attachment_list(zFullName, "<hr /><h2>Attachments:</h2><ul>");
}
style_footer();
}
/*
** TH1 command: append_field FIELD STRING
**
** FIELD is the name of a database column to which we might want
** to append text. STRING is the text to be appended to that
** column. The append does not actually occur until the
** submit_ticket command is run.
*/
static int appendRemarkCmd(
|
| ︙ | ︙ |
Changes to test/th1.test.
| ︙ | ︙ | |||
628 629 630 631 632 633 634 |
fossil test-th-eval "setParameter test4 value4; setParameter test4 value5; getParameter test4"
test th1-set-parameter-6 {$RESULT eq {value5}}
###############################################################################
fossil test-th-eval "setParameter test4 value4; setParameter test4 value5; getParameter test4 defValue4"
test th1-set-parameter-7 {$RESULT eq {value5}}
| > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 |
fossil test-th-eval "setParameter test4 value4; setParameter test4 value5; getParameter test4"
test th1-set-parameter-6 {$RESULT eq {value5}}
###############################################################################
fossil test-th-eval "setParameter test4 value4; setParameter test4 value5; getParameter test4 defValue4"
test th1-set-parameter-7 {$RESULT eq {value5}}
###############################################################################
fossil test-th-eval "artifact"
test th1-artifact-1 {$RESULT eq \
{TH_ERROR: wrong # args: should be "artifact ID"}}
###############################################################################
fossil test-th-eval "artifact tip"
test th1-artifact-2 {$RESULT eq {TH_ERROR: repository unavailable}}
###############################################################################
fossil test-th-eval --th-open-config "artifact tip"
test th1-artifact-3 {[regexp -- {F test/th1\.test [0-9a-f]{40}} $RESULT]}
###############################################################################
fossil test-th-eval "artifact 0000000000"
test th1-artifact-4 {$RESULT eq {TH_ERROR: repository unavailable}}
###############################################################################
fossil test-th-eval --th-open-config "artifact 0000000000"
test th1-artifact-5 {$RESULT eq {TH_ERROR: artifact not found}}
|
Changes to www/changes.wiki.
1 2 3 4 5 6 7 8 9 10 11 12 |
<title>Change Log</title>
<h2>Changes For Version 1.30 (as yet unreleased)</h2>
* Add setting to control the number of times autosync will be tried before
returning an error.
* Add the "fossil fusefs DIRECTORY" command that mounts a Fuse Filesystem
at the given DIRECTORY and populates it with read-only copies of all
historical check-ins. This only works on systems that support FuseFS.
* Support customization of commands and webpages, including the ability to
add new ones, via the "TH1 hooks" feature. Disabled by default. Enabled
via a compile-time option.
* Add the <nowiki>[checkout], [render], [styleHeader], [styleFooter],
| | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
<title>Change Log</title>
<h2>Changes For Version 1.30 (as yet unreleased)</h2>
* Add setting to control the number of times autosync will be tried before
returning an error.
* Add the "fossil fusefs DIRECTORY" command that mounts a Fuse Filesystem
at the given DIRECTORY and populates it with read-only copies of all
historical check-ins. This only works on systems that support FuseFS.
* Support customization of commands and webpages, including the ability to
add new ones, via the "TH1 hooks" feature. Disabled by default. Enabled
via a compile-time option.
* Add the <nowiki>[checkout], [render], [styleHeader], [styleFooter],
[trace], [getParameter], [setParameter], and [artifact]</nowiki> commands
to TH1, primarily for use by TH1 hooks.
* Bring in the latest version of autosetup from upstream.
<h2>Changes For Version 1.29 (2014-06-12)</h2>
* Add the ability to display content, diffs and annotations for UTF16
text files in the web interface.
* Add the "SaveAs..." and "Invert" buttons
to the graphical diff display that results
|
| ︙ | ︙ |