Fossil SCM

fossil-scm / src / hook.c
Source Blame History 546 lines
9d8db79… drh 1 /*
9d8db79… drh 2 ** Copyright (c) 2020 D. Richard Hipp
9d8db79… drh 3 **
9d8db79… drh 4 ** This program is free software; you can redistribute it and/or
9d8db79… drh 5 ** modify it under the terms of the Simplified BSD License (also
9d8db79… drh 6 ** known as the "2-Clause License" or "FreeBSD License".)
9d8db79… drh 7 **
9d8db79… drh 8 ** This program is distributed in the hope that it will be useful,
9d8db79… drh 9 ** but without any warranty; without even the implied warranty of
9d8db79… drh 10 ** merchantability or fitness for a particular purpose.
9d8db79… drh 11 **
9d8db79… drh 12 ** Author contact information:
9d8db79… drh 13 ** [email protected]
9d8db79… drh 14 ** http://www.hwaci.com/drh/
9d8db79… drh 15 **
9d8db79… drh 16 *******************************************************************************
9d8db79… drh 17 **
9d8db79… drh 18 ** This file implements "hooks" - external programs that can be run
9d8db79… drh 19 ** when various events occur on a Fossil repository.
9d8db79… drh 20 **
9d8db79… drh 21 ** Hooks are stored in the following CONFIG variables:
9d8db79… drh 22 **
9d8db79… drh 23 ** hooks A JSON-array of JSON objects. Each object describes
9d8db79… drh 24 ** a single hook. Example:
9d8db79… drh 25 ** {
9d8db79… drh 26 ** "type": "after-receive", // type of hook
9d8db79… drh 27 ** "cmd": "command-to-run", // command to run
9d8db79… drh 28 ** "seq": 50 // run in this order
9d8db79… drh 29 ** }
9d8db79… drh 30 **
9d8db79… drh 31 ** hook-last-rcvid The last rcvid for which post-receive hooks were
9d8db79… drh 32 ** run.
9d8db79… drh 33 **
e2bdc10… danield 34 ** hook-embargo Do not run hooks again before this Julian day.
9d8db79… drh 35 **
9d8db79… drh 36 ** For "after-receive" hooks, a list of the received artifacts is sent
9d8db79… drh 37 ** into the command via standard input. Each line of input begins with
9d8db79… drh 38 ** the hash of the artifact and continues with a description of the
9d8db79… drh 39 ** interpretation of the artifact.
9d8db79… drh 40 */
9d8db79… drh 41 #include "config.h"
9d8db79… drh 42 #include "hook.h"
9d8db79… drh 43
23f95bf… drh 44 /*
5d9a744… stephan 45 ** SETTING: hooks sensitive width=40 block-text
23f95bf… drh 46 ** The "hooks" setting contains JSON that describes all defined
23f95bf… drh 47 ** hooks. The value is an array of objects. Each object describes
23f95bf… drh 48 ** a single hook. Example:
23f95bf… drh 49 **
23f95bf… drh 50 **
23f95bf… drh 51 ** {
23f95bf… drh 52 ** "type": "after-receive", // type of hook
23f95bf… drh 53 ** "cmd": "command-to-run", // command to run
23f95bf… drh 54 ** "seq": 50 // run in this order
23f95bf… drh 55 ** }
23f95bf… drh 56 */
9d8db79… drh 57 /*
9d8db79… drh 58 ** List of valid hook types:
9d8db79… drh 59 */
9d8db79… drh 60 static const char *azType[] = {
9d8db79… drh 61 "after-receive",
9d8db79… drh 62 "before-commit",
9d8db79… drh 63 "disabled",
9d8db79… drh 64 };
9d8db79… drh 65
9d8db79… drh 66 /*
9d8db79… drh 67 ** Return true if zType is a valid hook type.
9d8db79… drh 68 */
9d8db79… drh 69 static int is_valid_hook_type(const char *zType){
9d8db79… drh 70 int i;
9d8db79… drh 71 for(i=0; i<count(azType); i++){
9d8db79… drh 72 if( strcmp(azType[i],zType)==0 ) return 1;
9d8db79… drh 73 }
9d8db79… drh 74 return 0;
9d8db79… drh 75 }
9d8db79… drh 76
9d8db79… drh 77 /*
9d8db79… drh 78 ** Throw an error if zType is not a valid hook type
9d8db79… drh 79 */
9d8db79… drh 80 static void validate_type(const char *zType){
9d8db79… drh 81 int i;
9d8db79… drh 82 char *zMsg;
9d8db79… drh 83 if( is_valid_hook_type(zType) ) return;
9d8db79… drh 84 zMsg = mprintf("\"%s\" is not a valid hook type - should be one of:", zType);
9d8db79… drh 85 for(i=0; i<count(azType); i++){
9d8db79… drh 86 zMsg = mprintf("%z %s", zMsg, azType[i]);
9d8db79… drh 87 }
9d8db79… drh 88 fossil_fatal("%s", zMsg);
9d8db79… drh 89 }
9d8db79… drh 90
9d8db79… drh 91 /*
9d8db79… drh 92 ** Translate a hook command string into its executable format by
9d8db79… drh 93 ** converting every %-substitutions as follows:
9d8db79… drh 94 **
9d8db79… drh 95 ** %F -> Name of the fossil executable
9d8db79… drh 96 ** %R -> Name of the repository
9d8db79… drh 97 ** %A -> Auxiliary information filename (might be empty string)
9d8db79… drh 98 **
9d8db79… drh 99 ** The returned string is obtained from fossil_malloc() and should
9d8db79… drh 100 ** be freed by the caller.
9d8db79… drh 101 */
9d8db79… drh 102 static char *hook_subst(
9d8db79… drh 103 const char *zCmd,
9d8db79… drh 104 const char *zAuxFilename /* Name of auxiliary information file */
9d8db79… drh 105 ){
9d8db79… drh 106 Blob r;
9d8db79… drh 107 int i;
9d8db79… drh 108 blob_init(&r, 0, 0);
9527034… drh 109 if( zCmd==0 ) return 0;
9d8db79… drh 110 while( zCmd[0] ){
9d8db79… drh 111 for(i=0; zCmd[i] && zCmd[i]!='%'; i++){}
9d8db79… drh 112 blob_append(&r, zCmd, i);
9d8db79… drh 113 if( zCmd[i]==0 ) break;
9d8db79… drh 114 if( zCmd[i+1]=='F' ){
9d8db79… drh 115 blob_append(&r, g.nameOfExe, -1);
9d8db79… drh 116 zCmd += i+2;
9d8db79… drh 117 }else if( zCmd[i+1]=='R' ){
9d8db79… drh 118 blob_append(&r, g.zRepositoryName, -1);
9d8db79… drh 119 zCmd += i+2;
9d8db79… drh 120 }else if( zCmd[i+1]=='A' ){
9d8db79… drh 121 if( zAuxFilename ) blob_append(&r, zAuxFilename, -1);
9d8db79… drh 122 zCmd += i+2;
9d8db79… drh 123 }else{
9d8db79… drh 124 blob_append(&r, zCmd+i, 1);
9d8db79… drh 125 zCmd += i+1;
9d8db79… drh 126 }
9d8db79… drh 127 }
9d8db79… drh 128 blob_str(&r);
9d8db79… drh 129 return r.aData;
9d8db79… drh 130 }
9d8db79… drh 131
9d8db79… drh 132 /*
9d8db79… drh 133 ** Record the fact that new artifacts are expected within N seconds
9d8db79… drh 134 ** (N is normally a small number) and so post-receive hooks should
9d8db79… drh 135 ** probably be deferred until after the new artifacts arrive.
9d8db79… drh 136 **
9d8db79… drh 137 ** If N==0, then there is no expectation of new artifacts arriving
9d8db79… drh 138 ** soon and so post-receive hooks can be run without delay.
9d8db79… drh 139 */
9d8db79… drh 140 void hook_expecting_more_artifacts(int N){
147bf47… drh 141 if( !db_is_writeable("repository") ){
147bf47… drh 142 /* No-op */
147bf47… drh 143 }else if( N>0 ){
f741baa… drh 144 db_unprotect(PROTECT_CONFIG);
9d8db79… drh 145 db_multi_exec(
9d8db79… drh 146 "REPLACE INTO config(name,value,mtime)"
9d8db79… drh 147 "VALUES('hook-embargo',now()+%d,now())",
9d8db79… drh 148 N
9d8db79… drh 149 );
f741baa… drh 150 db_protect_pop();
9d8db79… drh 151 }else{
9d8db79… drh 152 db_unset("hook-embargo",0);
9d8db79… drh 153 }
9d8db79… drh 154 }
9d8db79… drh 155
9d8db79… drh 156
9d8db79… drh 157 /*
9d8db79… drh 158 ** Fill the Blob pOut with text that describes all artifacts
9d8db79… drh 159 ** received after zBaseRcvid up to and including zNewRcvid.
9d8db79… drh 160 ** Except, never include more than one days worth of changes.
9d8db79… drh 161 **
9d8db79… drh 162 ** If zBaseRcvid is NULL, then use the "hook-last-rcvid" setting.
9d8db79… drh 163 ** If zNewRcvid is NULL, use the last available rcvid.
9d8db79… drh 164 */
9d8db79… drh 165 void hook_changes(Blob *pOut, const char *zBaseRcvid, const char *zNewRcvid){
9d8db79… drh 166 char *zWhere;
9d8db79… drh 167 Stmt q;
9d8db79… drh 168 if( zBaseRcvid==0 ){
9d8db79… drh 169 zBaseRcvid = db_get("hook-last-rcvid","0");
9d8db79… drh 170 }
9d8db79… drh 171 if( zNewRcvid==0 ){
9d8db79… drh 172 zNewRcvid = db_text("0","SELECT max(rcvid) FROM rcvfrom");
9d8db79… drh 173 }
9d8db79… drh 174
9d8db79… drh 175 /* Adjust the baseline rcvid to omit change that are more than
9d8db79… drh 176 ** 24 hours older than the most recent change.
9d8db79… drh 177 */
9d8db79… drh 178 zBaseRcvid = db_text(0,
9d8db79… drh 179 "SELECT min(rcvid) FROM rcvfrom"
9d8db79… drh 180 " WHERE rcvid>=%d"
9d8db79… drh 181 " AND mtime>=(SELECT mtime FROM rcvfrom WHERE rcvid=%d)-1.0",
9d8db79… drh 182 atoi(zBaseRcvid), atoi(zNewRcvid)
9d8db79… drh 183 );
9d8db79… drh 184
9d8db79… drh 185 zWhere = mprintf("IN (SELECT rid FROM blob WHERE rcvid>%d AND rcvid<=%d)",
9d8db79… drh 186 atoi(zBaseRcvid), atoi(zNewRcvid));
9d8db79… drh 187 describe_artifacts(zWhere);
9d8db79… drh 188 fossil_free(zWhere);
9d8db79… drh 189 db_prepare(&q, "SELECT uuid, summary FROM description");
9d8db79… drh 190 while( db_step(&q)==SQLITE_ROW ){
9d8db79… drh 191 blob_appendf(pOut, "%s %s\n", db_column_text(&q,0), db_column_text(&q,1));
9d8db79… drh 192 }
9d8db79… drh 193 db_finalize(&q);
9d8db79… drh 194 }
9d8db79… drh 195
9d8db79… drh 196 /*
84f697e… drh 197 ** COMMAND: hook*
9d8db79… drh 198 **
9d8db79… drh 199 ** Usage: %fossil hook COMMAND ...
9d8db79… drh 200 **
9d8db79… drh 201 ** Commands include:
9d8db79… drh 202 **
62cb8ea… drh 203 ** > fossil hook add --command COMMAND --type TYPE --sequence NUMBER
9d8db79… drh 204 **
9d8db79… drh 205 ** Create a new hook. The --command and --type arguments are
9d8db79… drh 206 ** required. --sequence is optional.
9d8db79… drh 207 **
62cb8ea… drh 208 ** > fossil hook delete ID ...
9d8db79… drh 209 **
9d8db79… drh 210 ** Delete one or more hooks by their IDs. ID can be "all"
9d8db79… drh 211 ** to delete all hooks. Caution: There is no "undo" for
9d8db79… drh 212 ** this operation. Deleted hooks are permanently lost.
9d8db79… drh 213 **
62cb8ea… drh 214 ** > fossil hook edit --command COMMAND --type TYPE --sequence NUMBER ID ...
9d8db79… drh 215 **
9d8db79… drh 216 ** Make changes to one or more existing hooks. The ID argument
9d8db79… drh 217 ** is either a hook-id, or a list of hook-ids, or the keyword
9d8db79… drh 218 ** "all". For example, to disable hook number 2, use:
9d8db79… drh 219 **
9d8db79… drh 220 ** fossil hook edit --type disabled 2
9d8db79… drh 221 **
62cb8ea… drh 222 ** > fossil hook list
9d8db79… drh 223 **
9d8db79… drh 224 ** Show all current hooks
9d8db79… drh 225 **
62cb8ea… drh 226 ** > fossil hook status
9d8db79… drh 227 **
9d8db79… drh 228 ** Print the values of CONFIG table entries that are relevant to
9d8db79… drh 229 ** hook processing. Used for debugging.
9d8db79… drh 230 **
62cb8ea… drh 231 ** > fossil hook test [OPTIONS] ID
9d8db79… drh 232 **
9d8db79… drh 233 ** Run the hook script given by ID for testing purposes.
9d8db79… drh 234 ** Options:
9d8db79… drh 235 **
9d8db79… drh 236 ** --dry-run Print the script on stdout rather than run it
cb283ca… km 237 ** --base-rcvid N Pretend that the hook-last-rcvid value is N
277437d… stephan 238 ** --new-rcvid M Pretend that the last rcvid value is M
9d8db79… drh 239 ** --aux-file NAME NAME is substituted for %A in the script
9d8db79… drh 240 **
9d8db79… drh 241 ** The --base-rcvid and --new-rcvid options are silently ignored if
9d8db79… drh 242 ** the hook type is not "after-receive". The default values for
2f78b2c… danield 243 ** --base-rcvid and --new-rcvid cause the last receive to be processed.
9d8db79… drh 244 */
9d8db79… drh 245 void hook_cmd(void){
9d8db79… drh 246 const char *zCmd;
9d8db79… drh 247 int nCmd;
9d8db79… drh 248 db_find_and_open_repository(0, 0);
9d8db79… drh 249 if( g.argc<3 ){
9d8db79… drh 250 usage("SUBCOMMAND ...");
9d8db79… drh 251 }
9d8db79… drh 252 zCmd = g.argv[2];
9d8db79… drh 253 nCmd = (int)strlen(zCmd);
9d8db79… drh 254 if( strncmp(zCmd, "add", nCmd)==0 ){
9d8db79… drh 255 const char *zCmd = find_option("command",0,1);
9d8db79… drh 256 const char *zType = find_option("type",0,1);
9d8db79… drh 257 const char *zSeq = find_option("sequence",0,1);
9d8db79… drh 258 int nSeq;
9d8db79… drh 259 verify_all_options();
9d8db79… drh 260 if( zCmd==0 || zType==0 ){
9d8db79… drh 261 fossil_fatal("the --command and --type options are required");
9d8db79… drh 262 }
9d8db79… drh 263 validate_type(zType);
9d8db79… drh 264 nSeq = zSeq ? atoi(zSeq) : 10;
9d8db79… drh 265 db_begin_write();
f741baa… drh 266 db_unprotect(PROTECT_CONFIG);
9d8db79… drh 267 db_multi_exec(
9d8db79… drh 268 "INSERT OR IGNORE INTO config(name,value) VALUES('hooks','[]');\n"
9d8db79… drh 269 "UPDATE config"
9d8db79… drh 270 " SET value=json_insert("
9d8db79… drh 271 " CASE WHEN json_valid(value) THEN value ELSE '[]' END,'$[#]',"
9d8db79… drh 272 " json_object('cmd',%Q,'type',%Q,'seq',%d)),"
9d8db79… drh 273 " mtime=now()"
9d8db79… drh 274 " WHERE name='hooks';",
9d8db79… drh 275 zCmd, zType, nSeq
9d8db79… drh 276 );
f741baa… drh 277 db_protect_pop();
9d8db79… drh 278 db_commit_transaction();
9d8db79… drh 279 }else
9d8db79… drh 280 if( strncmp(zCmd, "edit", nCmd)==0 ){
9d8db79… drh 281 const char *zCmd = find_option("command",0,1);
9d8db79… drh 282 const char *zType = find_option("type",0,1);
9d8db79… drh 283 const char *zSeq = find_option("sequence",0,1);
9d8db79… drh 284 int nSeq;
9d8db79… drh 285 int i;
9d8db79… drh 286 verify_all_options();
9d8db79… drh 287 if( zCmd==0 && zType==0 && zSeq==0 ){
9d8db79… drh 288 fossil_fatal("at least one of --command, --type, or --sequence"
9d8db79… drh 289 " is required");
9d8db79… drh 290 }
9d8db79… drh 291 if( zType ) validate_type(zType);
9d8db79… drh 292 nSeq = zSeq ? atoi(zSeq) : 10;
9d8db79… drh 293 if( g.argc<4 ) usage("delete ID ...");
9d8db79… drh 294 db_begin_write();
9d8db79… drh 295 for(i=3; i<g.argc; i++){
9d8db79… drh 296 Blob sql;
0cb6e03… ashepilko 297 int id;
9d8db79… drh 298 if( sqlite3_strglob("*[^0-9]*", g.argv[i])==0 ){
9d8db79… drh 299 fossil_fatal("not a valid ID: \"%s\"", g.argv[i]);
9d8db79… drh 300 }
0cb6e03… ashepilko 301 id = atoi(g.argv[i]);
9d8db79… drh 302 blob_init(&sql, 0, 0);
9d8db79… drh 303 blob_append_sql(&sql, "UPDATE config SET mtime=now(), value="
9d8db79… drh 304 "json_replace(CASE WHEN json_valid(value) THEN value ELSE '[]' END");
9d8db79… drh 305 if( zCmd ){
9d8db79… drh 306 blob_append_sql(&sql, ",'$[%d].cmd',%Q", id, zCmd);
9d8db79… drh 307 }
9d8db79… drh 308 if( zType ){
9d8db79… drh 309 blob_append_sql(&sql, ",'$[%d].type',%Q", id, zType);
9d8db79… drh 310 }
9d8db79… drh 311 if( zSeq ){
9d8db79… drh 312 blob_append_sql(&sql, ",'$[%d].seq',%d", id, nSeq);
9d8db79… drh 313 }
9d8db79… drh 314 blob_append_sql(&sql,") WHERE name='hooks';");
f741baa… drh 315 db_unprotect(PROTECT_CONFIG);
9d8db79… drh 316 db_multi_exec("%s", blob_sql_text(&sql));
f741baa… drh 317 db_protect_pop();
9d8db79… drh 318 blob_reset(&sql);
9d8db79… drh 319 }
9d8db79… drh 320 db_commit_transaction();
9d8db79… drh 321 }else
9d8db79… drh 322 if( strncmp(zCmd, "delete", nCmd)==0 ){
9d8db79… drh 323 int i;
9d8db79… drh 324 verify_all_options();
9d8db79… drh 325 if( g.argc<4 ) usage("delete ID ...");
9d8db79… drh 326 db_begin_write();
f741baa… drh 327 db_unprotect(PROTECT_CONFIG);
9d8db79… drh 328 db_multi_exec(
9d8db79… drh 329 "INSERT OR IGNORE INTO config(name,value) VALUES('hooks','[]');\n"
9d8db79… drh 330 );
9d8db79… drh 331 for(i=3; i<g.argc; i++){
9d8db79… drh 332 const char *zId = g.argv[i];
9d8db79… drh 333 if( strcmp(zId,"all")==0 ){
faa39ea… stephan 334 db_unprotect(PROTECT_ALL);
9d8db79… drh 335 db_set("hooks","[]", 0);
faa39ea… stephan 336 db_protect_pop();
9d8db79… drh 337 break;
9d8db79… drh 338 }
9d8db79… drh 339 if( sqlite3_strglob("*[^0-9]*", g.argv[i])==0 ){
9d8db79… drh 340 fossil_fatal("not a valid ID: \"%s\"", g.argv[i]);
9d8db79… drh 341 }
9d8db79… drh 342 db_multi_exec(
9d8db79… drh 343 "UPDATE config"
9d8db79… drh 344 " SET value=json_remove("
9d8db79… drh 345 " CASE WHEN json_valid(value) THEN value ELSE '[]' END,'$[%d]'),"
9d8db79… drh 346 " mtime=now()"
9d8db79… drh 347 " WHERE name='hooks';",
9d8db79… drh 348 atoi(zId)
9d8db79… drh 349 );
9d8db79… drh 350 }
f741baa… drh 351 db_protect_pop();
9d8db79… drh 352 db_commit_transaction();
9d8db79… drh 353 }else
9d8db79… drh 354 if( strncmp(zCmd, "list", nCmd)==0 ){
9d8db79… drh 355 Stmt q;
9d8db79… drh 356 int n = 0;
0cb6e03… ashepilko 357 verify_all_options();
9d8db79… drh 358 db_prepare(&q,
9d8db79… drh 359 "SELECT jx.key,"
50d433e… drh 360 " jx.value->>'seq',"
50d433e… drh 361 " jx.value->>'cmd',"
50d433e… drh 362 " jx.value->>'type'"
9d8db79… drh 363 " FROM config, json_each(config.value) AS jx"
9d8db79… drh 364 " WHERE config.name='hooks' AND json_valid(config.value)"
9d8db79… drh 365 );
9d8db79… drh 366 while( db_step(&q)==SQLITE_ROW ){
9d8db79… drh 367 if( n++ ) fossil_print("\n");
9d8db79… drh 368 fossil_print("%3d: type = %s\n",
9d8db79… drh 369 db_column_int(&q,0), db_column_text(&q,3));
9d8db79… drh 370 fossil_print(" command = %s\n", db_column_text(&q,2));
9d8db79… drh 371 fossil_print(" sequence = %d\n", db_column_int(&q,1));
9d8db79… drh 372 }
9d8db79… drh 373 db_finalize(&q);
9d8db79… drh 374 }else
9d8db79… drh 375 if( strncmp(zCmd, "status", nCmd)==0 ){
9d8db79… drh 376 Stmt q;
9d8db79… drh 377 db_prepare(&q,
9d8db79… drh 378 "SELECT name, quote(value) FROM config WHERE name IN"
9d8db79… drh 379 "('hooks','hook-embargo','hook-last-rcvid') ORDER BY name"
9d8db79… drh 380 );
9d8db79… drh 381 while( db_step(&q)==SQLITE_ROW ){
9d8db79… drh 382 fossil_print("%s: %s\n", db_column_text(&q,0), db_column_text(&q,1));
9d8db79… drh 383 }
9d8db79… drh 384 db_finalize(&q);
9d8db79… drh 385 }else
9d8db79… drh 386 if( strncmp(zCmd, "test", nCmd)==0 ){
9d8db79… drh 387 Stmt q;
0cb6e03… ashepilko 388 int id;
9d8db79… drh 389 int bDryRun = find_option("dry-run", "n", 0)!=0;
9d8db79… drh 390 const char *zOrigRcvid = find_option("base-rcvid",0,1);
9d8db79… drh 391 const char *zNewRcvid = find_option("new-rcvid",0,1);
9d8db79… drh 392 const char *zAuxFilename = find_option("aux-file",0,1);
9d8db79… drh 393 verify_all_options();
9d8db79… drh 394 if( g.argc<4 ) usage("test ID");
0cb6e03… ashepilko 395 id = atoi(g.argv[3]);
9d8db79… drh 396 if( zOrigRcvid==0 ){
9d8db79… drh 397 zOrigRcvid = db_text(0, "SELECT max(rcvid)-1 FROM rcvfrom");
9d8db79… drh 398 }
9d8db79… drh 399 db_prepare(&q,
50d433e… drh 400 "SELECT value->>'$[%d].cmd', value->>'$[%d].type'=='after-receive'"
9d8db79… drh 401 " FROM config"
9d8db79… drh 402 " WHERE name='hooks' AND json_valid(value)",
9d8db79… drh 403 id, id
9d8db79… drh 404 );
9d8db79… drh 405 while( db_step(&q)==SQLITE_ROW ){
9d8db79… drh 406 const char *zCmd = db_column_text(&q,0);
9d8db79… drh 407 char *zCmd2 = hook_subst(zCmd, zAuxFilename);
9d8db79… drh 408 int needOut = db_column_int(&q,1);
9d8db79… drh 409 Blob out;
9527034… drh 410 if( zCmd2==0 ) continue;
9d8db79… drh 411 blob_init(&out,0,0);
9d8db79… drh 412 if( needOut ) hook_changes(&out, zOrigRcvid, zNewRcvid);
9d8db79… drh 413 if( bDryRun ){
9d8db79… drh 414 fossil_print("%s\n", zCmd2);
9d8db79… drh 415 if( needOut ){
9d8db79… drh 416 fossil_print("%s", blob_str(&out));
9d8db79… drh 417 }
9d8db79… drh 418 }else if( needOut ){
9d8db79… drh 419 int fdFromChild;
9d8db79… drh 420 FILE *toChild;
9d8db79… drh 421 int pidChild;
9d8db79… drh 422 if( popen2(zCmd2, &fdFromChild, &toChild, &pidChild, 0)==0 ){
9d8db79… drh 423 if( toChild ){
9d8db79… drh 424 fwrite(blob_buffer(&out),1,blob_size(&out),toChild);
9d8db79… drh 425 }
9d8db79… drh 426 pclose2(fdFromChild, toChild, pidChild);
9d8db79… drh 427 }
9d8db79… drh 428 }else{
9d8db79… drh 429 fossil_system(zCmd2);
9d8db79… drh 430 }
9d8db79… drh 431 fossil_free(zCmd2);
9d8db79… drh 432 blob_reset(&out);
9d8db79… drh 433 }
9d8db79… drh 434 db_finalize(&q);
9d8db79… drh 435 }else
9d8db79… drh 436 {
9d8db79… drh 437 fossil_fatal("unknown command \"%s\" - should be one of: "
9d8db79… drh 438 "add delete edit list test", zCmd);
9d8db79… drh 439 }
9d8db79… drh 440 }
9d8db79… drh 441
9d8db79… drh 442 /*
9d8db79… drh 443 ** The backoffice calls this routine to run the after-receive hooks.
9d8db79… drh 444 */
9d8db79… drh 445 int hook_backoffice(void){
9d8db79… drh 446 Stmt q;
9d8db79… drh 447 const char *zLastRcvid = 0;
9d8db79… drh 448 char *zNewRcvid = 0;
9d8db79… drh 449 Blob chng;
9d8db79… drh 450 int cnt = 0;
9d8db79… drh 451 db_begin_write();
9d8db79… drh 452 if( !db_exists("SELECT 1 FROM config WHERE name='hooks'") ){
9d8db79… drh 453 goto hook_backoffice_done; /* No hooks */
9d8db79… drh 454 }
9d8db79… drh 455 if( db_int(0, "SELECT now()<value+0 FROM config"
9d8db79… drh 456 " WHERE name='hook-embargo'") ){
9d8db79… drh 457 goto hook_backoffice_done; /* Within the embargo window */
9d8db79… drh 458 }
9d8db79… drh 459 zLastRcvid = db_get("hook-last-rcvid","0");
9d8db79… drh 460 zNewRcvid = db_text("0","SELECT max(rcvid) FROM rcvfrom");
9d8db79… drh 461 if( atoi(zLastRcvid)>=atoi(zNewRcvid) ){
9d8db79… drh 462 goto hook_backoffice_done; /* no new content */
9d8db79… drh 463 }
9d8db79… drh 464 blob_init(&chng, 0, 0);
9d8db79… drh 465 db_prepare(&q,
50d433e… drh 466 "SELECT jx.value->>'cmd'"
9d8db79… drh 467 " FROM config, json_each(config.value) AS jx"
9d8db79… drh 468 " WHERE config.name='hooks' AND json_valid(config.value)"
50d433e… drh 469 " AND jx.value->>'type'='after-receive'"
50d433e… drh 470 " ORDER BY jx.value->>'seq';"
9d8db79… drh 471 );
9d8db79… drh 472 while( db_step(&q)==SQLITE_ROW ){
9d8db79… drh 473 char *zCmd;
9d8db79… drh 474 int fdFromChild;
9d8db79… drh 475 FILE *toChild;
9d8db79… drh 476 int childPid;
9d8db79… drh 477 if( cnt==0 ){
9d8db79… drh 478 hook_changes(&chng, zLastRcvid, 0);
9d8db79… drh 479 }
9d8db79… drh 480 zCmd = hook_subst(db_column_text(&q,0), 0);
e6aaeb1… jan 481 if( popen2(zCmd, &fdFromChild, &toChild, &childPid, 0)==0 ){
9d8db79… drh 482 if( toChild ){
9d8db79… drh 483 fwrite(blob_buffer(&chng),1,blob_size(&chng),toChild);
9d8db79… drh 484 }
9d8db79… drh 485 pclose2(fdFromChild, toChild, childPid);
9d8db79… drh 486 }
9d8db79… drh 487 fossil_free(zCmd);
9d8db79… drh 488 cnt++;
9d8db79… drh 489 }
9d8db79… drh 490 db_finalize(&q);
9d8db79… drh 491 db_set("hook-last-rcvid", zNewRcvid, 0);
9d8db79… drh 492 blob_reset(&chng);
9d8db79… drh 493 hook_backoffice_done:
9d8db79… drh 494 db_commit_transaction();
9d8db79… drh 495 return cnt;
9d8db79… drh 496 }
9d8db79… drh 497
9d8db79… drh 498 /*
23f95bf… drh 499 ** Return true if one or more hooks of type zType exit.
23f95bf… drh 500 */
23f95bf… drh 501 int hook_exists(const char *zType){
23f95bf… drh 502 return db_exists(
23f95bf… drh 503 "SELECT 1"
23f95bf… drh 504 " FROM config, json_each(config.value) AS jx"
23f95bf… drh 505 " WHERE config.name='hooks' AND json_valid(config.value)"
50d433e… drh 506 " AND jx.value->>'type'=%Q;",
23f95bf… drh 507 zType
23f95bf… drh 508 );
23f95bf… drh 509 }
23f95bf… drh 510
23f95bf… drh 511 /*
9d8db79… drh 512 ** Run all hooks of type zType. Use zAuxFile as the auxiliary information
9d8db79… drh 513 ** file.
9d8db79… drh 514 **
9d8db79… drh 515 ** If any hook returns non-zero, then stop running and return non-zero.
9d8db79… drh 516 ** Return zero only if all hooks return zero.
9d8db79… drh 517 */
9d8db79… drh 518 int hook_run(const char *zType, const char *zAuxFile, int traceFlag){
9d8db79… drh 519 Stmt q;
9d8db79… drh 520 int rc = 0;
9d8db79… drh 521 if( !db_exists("SELECT 1 FROM config WHERE name='hooks'") ){
9d8db79… drh 522 return 0;
9d8db79… drh 523 }
9d8db79… drh 524 db_prepare(&q,
50d433e… drh 525 "SELECT jx.value->>'cmd' "
9d8db79… drh 526 " FROM config, json_each(config.value) AS jx"
9d8db79… drh 527 " WHERE config.name='hooks' AND json_valid(config.value)"
50d433e… drh 528 " AND jx.value->>'type'==%Q"
50d433e… drh 529 " ORDER BY jx.value->'seq';",
9d8db79… drh 530 zType
9d8db79… drh 531 );
9d8db79… drh 532 while( db_step(&q)==SQLITE_ROW ){
9d8db79… drh 533 char *zCmd;
9d8db79… drh 534 zCmd = hook_subst(db_column_text(&q,0), zAuxFile);
9d8db79… drh 535 if( traceFlag ){
9d8db79… drh 536 fossil_print("%s hook: %s\n", zType, zCmd);
9d8db79… drh 537 }
9d8db79… drh 538 rc = fossil_system(zCmd);
9d8db79… drh 539 fossil_free(zCmd);
9d8db79… drh 540 if( rc ){
9d8db79… drh 541 break;
9d8db79… drh 542 }
9d8db79… drh 543 }
9d8db79… drh 544 db_finalize(&q);
9d8db79… drh 545 return rc;
9d8db79… drh 546 }

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button