Fossil SCM

Add support for before-commit hooks.

drh 2020-07-06 16:20 hooks
Commit 4a9c6c64ee77c6de7952de2be0e104166afb8be462da472e229b9083e682f9e4
2 files changed +76 -3 +52 -6
+76 -3
--- src/checkin.c
+++ src/checkin.c
@@ -1323,10 +1323,66 @@
13231323
);
13241324
}
13251325
prompt_for_user_comment(pComment, &prompt);
13261326
blob_reset(&prompt);
13271327
}
1328
+
1329
+/*
1330
+** Prepare text that describes a pending commit and write it into
1331
+** a file at the root of the check-in. Return the name of that file.
1332
+**
1333
+** Space to hold the returned filename is obtained from fossil_malloc()
1334
+** and should be freed by the caller. The caller should also unlink
1335
+** the file when it is done.
1336
+*/
1337
+static char *prepare_commit_description_file(
1338
+ CheckinInfo *p, /* Information about this commit */
1339
+ int parent_rid /* parent check-in */
1340
+){
1341
+ Blob *pDesc;
1342
+ char *zTags;
1343
+ char *zFilename;
1344
+ Blob desc;
1345
+ unsigned int r[2];
1346
+ blob_init(&desc, 0, 0);
1347
+ pDesc = &desc;
1348
+ blob_appendf(pDesc, "checkout %s\n", g.zLocalRoot);
1349
+ blob_appendf(pDesc, "repository %s\n", g.zRepositoryName);
1350
+ blob_appendf(pDesc, "user %s\n",
1351
+ p->zUserOvrd ? p->zUserOvrd : login_name());
1352
+ blob_appendf(pDesc, "branch %s\n",
1353
+ (p->zBranch && p->zBranch[0]) ? p->zBranch : "trunk");
1354
+ zTags = info_tags_of_checkin(parent_rid, 1);
1355
+ if( zTags || p->azTag ){
1356
+ blob_append(pDesc, "tags ", -1);
1357
+ if(zTags){
1358
+ blob_appendf(pDesc, "%z%s", zTags, p->azTag ? ", " : "");
1359
+ }
1360
+ if(p->azTag){
1361
+ int i = 0;
1362
+ for( ; p->azTag[i]; ++i ){
1363
+ blob_appendf(pDesc, "%s%s", p->azTag[i],
1364
+ p->azTag[i+1] ? ", " : "");
1365
+ }
1366
+ }
1367
+ blob_appendf(pDesc, "\n");
1368
+ }
1369
+ status_report(pDesc, C_DEFAULT | C_FATAL);
1370
+ if( g.markPrivate ){
1371
+ blob_append(pDesc, "private-branch\n", -1);
1372
+ }
1373
+ if( p->integrateFlag ){
1374
+ blob_append(pDesc, "integrate\n", -1);
1375
+ }
1376
+ sqlite3_randomness(sizeof(r), r);
1377
+ zFilename = mprintf("%scommit-description-%08x%08x.txt",
1378
+ g.zLocalRoot, r[0], r[1]);
1379
+ blob_write_to_file(pDesc, zFilename);
1380
+ blob_reset(pDesc);
1381
+ return zFilename;
1382
+}
1383
+
13281384
13291385
/*
13301386
** Populate the Global.aCommitFile[] based on the command line arguments
13311387
** to a [commit] command. Global.aCommitFile is an array of integers
13321388
** sized at (N+1), where N is the number of arguments passed to [commit].
@@ -2008,27 +2064,29 @@
20082064
** --baseline use a baseline manifest in the commit process
20092065
** --bgcolor COLOR apply COLOR to this one check-in only
20102066
** --branch NEW-BRANCH-NAME check in to this new branch
20112067
** --branchcolor COLOR apply given COLOR to the branch
20122068
** --close close the branch being committed
2069
+** --date-override DATETIME DATE to use instead of 'now'
20132070
** --delta use a delta manifest in the commit process
2071
+** --hash verify file status using hashing rather
2072
+** than relying on file mtimes
20142073
** --integrate close all merged-in branches
20152074
** -m|--comment COMMENT-TEXT use COMMENT-TEXT as commit comment
20162075
** -M|--message-file FILE read the commit comment from given file
20172076
** --mimetype MIMETYPE mimetype of check-in comment
20182077
** -n|--dry-run If given, display instead of run actions
20192078
** --no-prompt This option disables prompting the user for
20202079
** input and assumes an answer of 'No' for every
20212080
** question.
20222081
** --no-warnings omit all warnings about file contents
2082
+** --no-verify do not run before-commit hooks
20232083
** --nosign do not attempt to sign this commit with gpg
20242084
** --override-lock allow a check-in even though parent is locked
20252085
** --private do not sync changes and their descendants
2026
-** --hash verify file status using hashing rather
2027
-** than relying on file mtimes
20282086
** --tag TAG-NAME assign given tag TAG-NAME to the check-in
2029
-** --date-override DATETIME DATE to use instead of 'now'
2087
+** --trace debug tracing.
20302088
** --user-override USER USER to use instead of the current default
20312089
**
20322090
** DATETIME may be "now" or "YYYY-MM-DDTHH:MM:SS.SSS". If in
20332091
** year-month-day form, it may be truncated, the "T" may be replaced by
20342092
** a space, and it may also name a timezone offset from UTC as "-HH:MM"
@@ -2050,10 +2108,12 @@
20502108
int noSign = 0; /* True to omit signing the manifest using GPG */
20512109
int privateFlag = 0; /* True if the --private option is present */
20522110
int privateParent = 0; /* True if the parent check-in is private */
20532111
int isAMerge = 0; /* True if checking in a merge */
20542112
int noWarningFlag = 0; /* True if skipping all warnings */
2113
+ int noVerify = 0; /* Do not run before-commit hooks */
2114
+ int bTrace = 0; /* Debug tracing */
20552115
int noPrompt = 0; /* True if skipping all prompts */
20562116
int forceFlag = 0; /* Undocumented: Disables all checks */
20572117
int forceDelta = 0; /* Force a delta-manifest */
20582118
int forceBaseline = 0; /* Force a baseline-manifest */
20592119
int allowConflict = 0; /* Allow unresolve merge conflicts */
@@ -2109,10 +2169,12 @@
21092169
allowFork = find_option("allow-fork",0,0)!=0;
21102170
if( find_option("override-lock",0,0)!=0 ) allowFork = 1;
21112171
allowOlder = find_option("allow-older",0,0)!=0;
21122172
noPrompt = find_option("no-prompt", 0, 0)!=0;
21132173
noWarningFlag = find_option("no-warnings", 0, 0)!=0;
2174
+ noVerify = find_option("no-verify",0,0)!=0;
2175
+ bTrace = find_option("trace",0,0)!=0;
21142176
sCiInfo.zBranch = find_option("branch","b",1);
21152177
sCiInfo.zColor = find_option("bgcolor",0,1);
21162178
sCiInfo.zBrClr = find_option("branchcolor",0,1);
21172179
sCiInfo.closeFlag = find_option("close",0,0)!=0;
21182180
sCiInfo.integrateFlag = find_option("integrate",0,0)!=0;
@@ -2340,10 +2402,21 @@
23402402
fossil_fatal("cannot commit against a closed leaf");
23412403
}
23422404
23432405
/* Always exit the loop on the second pass */
23442406
if( bRecheck ) break;
2407
+
2408
+ /* Run before-commit hooks */
2409
+ if( !noVerify ){
2410
+ char *zAuxFile = prepare_commit_description_file(&sCiInfo,vid);
2411
+ int rc = hook_run("before-commit",zAuxFile,bTrace);
2412
+ file_delete(zAuxFile);
2413
+ fossil_free(zAuxFile);
2414
+ if( rc ){
2415
+ fossil_fatal("Before-commit hook failed\n");
2416
+ }
2417
+ }
23452418
23462419
/* Get the check-in comment. This might involve prompting the
23472420
** user for the check-in comment, in which case we should resync
23482421
** to renew the check-in lock and repeat the checks for conflicts.
23492422
*/
23502423
--- src/checkin.c
+++ src/checkin.c
@@ -1323,10 +1323,66 @@
1323 );
1324 }
1325 prompt_for_user_comment(pComment, &prompt);
1326 blob_reset(&prompt);
1327 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1328
1329 /*
1330 ** Populate the Global.aCommitFile[] based on the command line arguments
1331 ** to a [commit] command. Global.aCommitFile is an array of integers
1332 ** sized at (N+1), where N is the number of arguments passed to [commit].
@@ -2008,27 +2064,29 @@
2008 ** --baseline use a baseline manifest in the commit process
2009 ** --bgcolor COLOR apply COLOR to this one check-in only
2010 ** --branch NEW-BRANCH-NAME check in to this new branch
2011 ** --branchcolor COLOR apply given COLOR to the branch
2012 ** --close close the branch being committed
 
2013 ** --delta use a delta manifest in the commit process
 
 
2014 ** --integrate close all merged-in branches
2015 ** -m|--comment COMMENT-TEXT use COMMENT-TEXT as commit comment
2016 ** -M|--message-file FILE read the commit comment from given file
2017 ** --mimetype MIMETYPE mimetype of check-in comment
2018 ** -n|--dry-run If given, display instead of run actions
2019 ** --no-prompt This option disables prompting the user for
2020 ** input and assumes an answer of 'No' for every
2021 ** question.
2022 ** --no-warnings omit all warnings about file contents
 
2023 ** --nosign do not attempt to sign this commit with gpg
2024 ** --override-lock allow a check-in even though parent is locked
2025 ** --private do not sync changes and their descendants
2026 ** --hash verify file status using hashing rather
2027 ** than relying on file mtimes
2028 ** --tag TAG-NAME assign given tag TAG-NAME to the check-in
2029 ** --date-override DATETIME DATE to use instead of 'now'
2030 ** --user-override USER USER to use instead of the current default
2031 **
2032 ** DATETIME may be "now" or "YYYY-MM-DDTHH:MM:SS.SSS". If in
2033 ** year-month-day form, it may be truncated, the "T" may be replaced by
2034 ** a space, and it may also name a timezone offset from UTC as "-HH:MM"
@@ -2050,10 +2108,12 @@
2050 int noSign = 0; /* True to omit signing the manifest using GPG */
2051 int privateFlag = 0; /* True if the --private option is present */
2052 int privateParent = 0; /* True if the parent check-in is private */
2053 int isAMerge = 0; /* True if checking in a merge */
2054 int noWarningFlag = 0; /* True if skipping all warnings */
 
 
2055 int noPrompt = 0; /* True if skipping all prompts */
2056 int forceFlag = 0; /* Undocumented: Disables all checks */
2057 int forceDelta = 0; /* Force a delta-manifest */
2058 int forceBaseline = 0; /* Force a baseline-manifest */
2059 int allowConflict = 0; /* Allow unresolve merge conflicts */
@@ -2109,10 +2169,12 @@
2109 allowFork = find_option("allow-fork",0,0)!=0;
2110 if( find_option("override-lock",0,0)!=0 ) allowFork = 1;
2111 allowOlder = find_option("allow-older",0,0)!=0;
2112 noPrompt = find_option("no-prompt", 0, 0)!=0;
2113 noWarningFlag = find_option("no-warnings", 0, 0)!=0;
 
 
2114 sCiInfo.zBranch = find_option("branch","b",1);
2115 sCiInfo.zColor = find_option("bgcolor",0,1);
2116 sCiInfo.zBrClr = find_option("branchcolor",0,1);
2117 sCiInfo.closeFlag = find_option("close",0,0)!=0;
2118 sCiInfo.integrateFlag = find_option("integrate",0,0)!=0;
@@ -2340,10 +2402,21 @@
2340 fossil_fatal("cannot commit against a closed leaf");
2341 }
2342
2343 /* Always exit the loop on the second pass */
2344 if( bRecheck ) break;
 
 
 
 
 
 
 
 
 
 
 
2345
2346 /* Get the check-in comment. This might involve prompting the
2347 ** user for the check-in comment, in which case we should resync
2348 ** to renew the check-in lock and repeat the checks for conflicts.
2349 */
2350
--- src/checkin.c
+++ src/checkin.c
@@ -1323,10 +1323,66 @@
1323 );
1324 }
1325 prompt_for_user_comment(pComment, &prompt);
1326 blob_reset(&prompt);
1327 }
1328
1329 /*
1330 ** Prepare text that describes a pending commit and write it into
1331 ** a file at the root of the check-in. Return the name of that file.
1332 **
1333 ** Space to hold the returned filename is obtained from fossil_malloc()
1334 ** and should be freed by the caller. The caller should also unlink
1335 ** the file when it is done.
1336 */
1337 static char *prepare_commit_description_file(
1338 CheckinInfo *p, /* Information about this commit */
1339 int parent_rid /* parent check-in */
1340 ){
1341 Blob *pDesc;
1342 char *zTags;
1343 char *zFilename;
1344 Blob desc;
1345 unsigned int r[2];
1346 blob_init(&desc, 0, 0);
1347 pDesc = &desc;
1348 blob_appendf(pDesc, "checkout %s\n", g.zLocalRoot);
1349 blob_appendf(pDesc, "repository %s\n", g.zRepositoryName);
1350 blob_appendf(pDesc, "user %s\n",
1351 p->zUserOvrd ? p->zUserOvrd : login_name());
1352 blob_appendf(pDesc, "branch %s\n",
1353 (p->zBranch && p->zBranch[0]) ? p->zBranch : "trunk");
1354 zTags = info_tags_of_checkin(parent_rid, 1);
1355 if( zTags || p->azTag ){
1356 blob_append(pDesc, "tags ", -1);
1357 if(zTags){
1358 blob_appendf(pDesc, "%z%s", zTags, p->azTag ? ", " : "");
1359 }
1360 if(p->azTag){
1361 int i = 0;
1362 for( ; p->azTag[i]; ++i ){
1363 blob_appendf(pDesc, "%s%s", p->azTag[i],
1364 p->azTag[i+1] ? ", " : "");
1365 }
1366 }
1367 blob_appendf(pDesc, "\n");
1368 }
1369 status_report(pDesc, C_DEFAULT | C_FATAL);
1370 if( g.markPrivate ){
1371 blob_append(pDesc, "private-branch\n", -1);
1372 }
1373 if( p->integrateFlag ){
1374 blob_append(pDesc, "integrate\n", -1);
1375 }
1376 sqlite3_randomness(sizeof(r), r);
1377 zFilename = mprintf("%scommit-description-%08x%08x.txt",
1378 g.zLocalRoot, r[0], r[1]);
1379 blob_write_to_file(pDesc, zFilename);
1380 blob_reset(pDesc);
1381 return zFilename;
1382 }
1383
1384
1385 /*
1386 ** Populate the Global.aCommitFile[] based on the command line arguments
1387 ** to a [commit] command. Global.aCommitFile is an array of integers
1388 ** sized at (N+1), where N is the number of arguments passed to [commit].
@@ -2008,27 +2064,29 @@
2064 ** --baseline use a baseline manifest in the commit process
2065 ** --bgcolor COLOR apply COLOR to this one check-in only
2066 ** --branch NEW-BRANCH-NAME check in to this new branch
2067 ** --branchcolor COLOR apply given COLOR to the branch
2068 ** --close close the branch being committed
2069 ** --date-override DATETIME DATE to use instead of 'now'
2070 ** --delta use a delta manifest in the commit process
2071 ** --hash verify file status using hashing rather
2072 ** than relying on file mtimes
2073 ** --integrate close all merged-in branches
2074 ** -m|--comment COMMENT-TEXT use COMMENT-TEXT as commit comment
2075 ** -M|--message-file FILE read the commit comment from given file
2076 ** --mimetype MIMETYPE mimetype of check-in comment
2077 ** -n|--dry-run If given, display instead of run actions
2078 ** --no-prompt This option disables prompting the user for
2079 ** input and assumes an answer of 'No' for every
2080 ** question.
2081 ** --no-warnings omit all warnings about file contents
2082 ** --no-verify do not run before-commit hooks
2083 ** --nosign do not attempt to sign this commit with gpg
2084 ** --override-lock allow a check-in even though parent is locked
2085 ** --private do not sync changes and their descendants
 
 
2086 ** --tag TAG-NAME assign given tag TAG-NAME to the check-in
2087 ** --trace debug tracing.
2088 ** --user-override USER USER to use instead of the current default
2089 **
2090 ** DATETIME may be "now" or "YYYY-MM-DDTHH:MM:SS.SSS". If in
2091 ** year-month-day form, it may be truncated, the "T" may be replaced by
2092 ** a space, and it may also name a timezone offset from UTC as "-HH:MM"
@@ -2050,10 +2108,12 @@
2108 int noSign = 0; /* True to omit signing the manifest using GPG */
2109 int privateFlag = 0; /* True if the --private option is present */
2110 int privateParent = 0; /* True if the parent check-in is private */
2111 int isAMerge = 0; /* True if checking in a merge */
2112 int noWarningFlag = 0; /* True if skipping all warnings */
2113 int noVerify = 0; /* Do not run before-commit hooks */
2114 int bTrace = 0; /* Debug tracing */
2115 int noPrompt = 0; /* True if skipping all prompts */
2116 int forceFlag = 0; /* Undocumented: Disables all checks */
2117 int forceDelta = 0; /* Force a delta-manifest */
2118 int forceBaseline = 0; /* Force a baseline-manifest */
2119 int allowConflict = 0; /* Allow unresolve merge conflicts */
@@ -2109,10 +2169,12 @@
2169 allowFork = find_option("allow-fork",0,0)!=0;
2170 if( find_option("override-lock",0,0)!=0 ) allowFork = 1;
2171 allowOlder = find_option("allow-older",0,0)!=0;
2172 noPrompt = find_option("no-prompt", 0, 0)!=0;
2173 noWarningFlag = find_option("no-warnings", 0, 0)!=0;
2174 noVerify = find_option("no-verify",0,0)!=0;
2175 bTrace = find_option("trace",0,0)!=0;
2176 sCiInfo.zBranch = find_option("branch","b",1);
2177 sCiInfo.zColor = find_option("bgcolor",0,1);
2178 sCiInfo.zBrClr = find_option("branchcolor",0,1);
2179 sCiInfo.closeFlag = find_option("close",0,0)!=0;
2180 sCiInfo.integrateFlag = find_option("integrate",0,0)!=0;
@@ -2340,10 +2402,21 @@
2402 fossil_fatal("cannot commit against a closed leaf");
2403 }
2404
2405 /* Always exit the loop on the second pass */
2406 if( bRecheck ) break;
2407
2408 /* Run before-commit hooks */
2409 if( !noVerify ){
2410 char *zAuxFile = prepare_commit_description_file(&sCiInfo,vid);
2411 int rc = hook_run("before-commit",zAuxFile,bTrace);
2412 file_delete(zAuxFile);
2413 fossil_free(zAuxFile);
2414 if( rc ){
2415 fossil_fatal("Before-commit hook failed\n");
2416 }
2417 }
2418
2419 /* Get the check-in comment. This might involve prompting the
2420 ** user for the check-in comment, in which case we should resync
2421 ** to renew the check-in lock and repeat the checks for conflicts.
2422 */
2423
+52 -6
--- src/hook.c
+++ src/hook.c
@@ -79,15 +79,19 @@
7979
** Translate a hook command string into its executable format by
8080
** converting every %-substitutions as follows:
8181
**
8282
** %F -> Name of the fossil executable
8383
** %R -> Name of the repository
84
+** %A -> Auxiliary information filename (might be empty string)
8485
**
8586
** The returned string is obtained from fossil_malloc() and should
8687
** be freed by the caller.
8788
*/
88
-char *hook_subst(const char *zCmd){
89
+static char *hook_subst(
90
+ const char *zCmd,
91
+ const char *zAuxFilename /* Name of auxiliary information file */
92
+){
8993
Blob r;
9094
int i;
9195
blob_init(&r, 0, 0);
9296
while( zCmd[0] ){
9397
for(i=0; zCmd[i] && zCmd[i]!='%'; i++){}
@@ -97,10 +101,13 @@
97101
blob_append(&r, g.nameOfExe, -1);
98102
zCmd += i+2;
99103
}else if( zCmd[i+1]=='R' ){
100104
blob_append(&r, g.zRepositoryName, -1);
101105
zCmd += i+2;
106
+ }else if( zCmd[i+1]=='A' ){
107
+ if( zAuxFilename ) blob_append(&r, zAuxFilename, -1);
108
+ zCmd += i+2;
102109
}else{
103110
blob_append(&r, zCmd+i, 1);
104111
zCmd += i+1;
105112
}
106113
}
@@ -206,13 +213,14 @@
206213
** > fossil hook test [OPTIONS] ID
207214
**
208215
** Run the hook script given by ID for testing purposes.
209216
** Options:
210217
**
211
-** --dry-run Print the script on stdout rather than run it
212
-** --base-rcvid N Pretend that the hook-last-rcvid value is N
213
-** --new-rcvid M Pretend that the last rcvid valud is M
218
+** --dry-run Print the script on stdout rather than run it
219
+** --base-rcvid N Pretend that the hook-last-rcvid value is N
220
+** --new-rcvid M Pretend that the last rcvid valud is M
221
+** --aux-file NAME NAME is substituted for %A in the script
214222
**
215223
** The --base-rcvid and --new-rcvid options are silently ignored if
216224
** the hook type is not "after-receive". The default values for
217225
** --base-rcvid and --new-rcvid cause the last recieve to be processed.
218226
*/
@@ -351,10 +359,11 @@
351359
if( strncmp(zCmd, "test", nCmd)==0 ){
352360
Stmt q;
353361
int bDryRun = find_option("dry-run", "n", 0)!=0;
354362
const char *zOrigRcvid = find_option("base-rcvid",0,1);
355363
const char *zNewRcvid = find_option("new-rcvid",0,1);
364
+ const char *zAuxFilename = find_option("aux-file",0,1);
356365
verify_all_options();
357366
if( g.argc<4 ) usage("test ID");
358367
int id = atoi(g.argv[3]);
359368
if( zOrigRcvid==0 ){
360369
zOrigRcvid = db_text(0, "SELECT max(rcvid)-1 FROM rcvfrom");
@@ -366,11 +375,11 @@
366375
" WHERE name='hooks' AND json_valid(value)",
367376
id, id
368377
);
369378
while( db_step(&q)==SQLITE_ROW ){
370379
const char *zCmd = db_column_text(&q,0);
371
- char *zCmd2 = hook_subst(zCmd);
380
+ char *zCmd2 = hook_subst(zCmd, zAuxFilename);
372381
int needOut = db_column_int(&q,1);
373382
Blob out;
374383
blob_init(&out,0,0);
375384
if( needOut ) hook_changes(&out, zOrigRcvid, zNewRcvid);
376385
if( bDryRun ){
@@ -432,11 +441,11 @@
432441
char *zCmd;
433442
FILE *f;
434443
if( cnt==0 ){
435444
hook_changes(&chng, zLastRcvid, 0);
436445
}
437
- zCmd = hook_subst(db_column_text(&q,0));
446
+ zCmd = hook_subst(db_column_text(&q,0), 0);
438447
f = popen(zCmd, "w");
439448
if( f ){
440449
fwrite(blob_buffer(&chng),1,blob_size(&chng),f);
441450
pclose(f);
442451
}
@@ -448,5 +457,42 @@
448457
blob_reset(&chng);
449458
hook_backoffice_done:
450459
db_commit_transaction();
451460
return cnt;
452461
}
462
+
463
+/*
464
+** Run all hooks of type zType. Use zAuxFile as the auxiliary information
465
+** file.
466
+**
467
+** If any hook returns non-zero, then stop running and return non-zero.
468
+** Return zero only if all hooks return zero.
469
+*/
470
+int hook_run(const char *zType, const char *zAuxFile, int traceFlag){
471
+ Stmt q;
472
+ int rc = 0;
473
+ if( !db_exists("SELECT 1 FROM config WHERE name='hooks'") ){
474
+ return 0;
475
+ }
476
+ db_prepare(&q,
477
+ "SELECT json_extract(jx.value,'$.cmd') "
478
+ " FROM config, json_each(config.value) AS jx"
479
+ " WHERE config.name='hooks' AND json_valid(config.value)"
480
+ " AND json_extract(jx.value,'$.type')=%Q"
481
+ " ORDER BY json_extract(jx.value,'$.seq');",
482
+ zType
483
+ );
484
+ while( db_step(&q)==SQLITE_ROW ){
485
+ char *zCmd;
486
+ zCmd = hook_subst(db_column_text(&q,0), zAuxFile);
487
+ if( traceFlag ){
488
+ fossil_print("%s hook: %s\n", zType, zCmd);
489
+ }
490
+ rc = fossil_system(zCmd);
491
+ fossil_free(zCmd);
492
+ if( rc ){
493
+ break;
494
+ }
495
+ }
496
+ db_finalize(&q);
497
+ return rc;
498
+}
453499
--- src/hook.c
+++ src/hook.c
@@ -79,15 +79,19 @@
79 ** Translate a hook command string into its executable format by
80 ** converting every %-substitutions as follows:
81 **
82 ** %F -> Name of the fossil executable
83 ** %R -> Name of the repository
 
84 **
85 ** The returned string is obtained from fossil_malloc() and should
86 ** be freed by the caller.
87 */
88 char *hook_subst(const char *zCmd){
 
 
 
89 Blob r;
90 int i;
91 blob_init(&r, 0, 0);
92 while( zCmd[0] ){
93 for(i=0; zCmd[i] && zCmd[i]!='%'; i++){}
@@ -97,10 +101,13 @@
97 blob_append(&r, g.nameOfExe, -1);
98 zCmd += i+2;
99 }else if( zCmd[i+1]=='R' ){
100 blob_append(&r, g.zRepositoryName, -1);
101 zCmd += i+2;
 
 
 
102 }else{
103 blob_append(&r, zCmd+i, 1);
104 zCmd += i+1;
105 }
106 }
@@ -206,13 +213,14 @@
206 ** > fossil hook test [OPTIONS] ID
207 **
208 ** Run the hook script given by ID for testing purposes.
209 ** Options:
210 **
211 ** --dry-run Print the script on stdout rather than run it
212 ** --base-rcvid N Pretend that the hook-last-rcvid value is N
213 ** --new-rcvid M Pretend that the last rcvid valud is M
 
214 **
215 ** The --base-rcvid and --new-rcvid options are silently ignored if
216 ** the hook type is not "after-receive". The default values for
217 ** --base-rcvid and --new-rcvid cause the last recieve to be processed.
218 */
@@ -351,10 +359,11 @@
351 if( strncmp(zCmd, "test", nCmd)==0 ){
352 Stmt q;
353 int bDryRun = find_option("dry-run", "n", 0)!=0;
354 const char *zOrigRcvid = find_option("base-rcvid",0,1);
355 const char *zNewRcvid = find_option("new-rcvid",0,1);
 
356 verify_all_options();
357 if( g.argc<4 ) usage("test ID");
358 int id = atoi(g.argv[3]);
359 if( zOrigRcvid==0 ){
360 zOrigRcvid = db_text(0, "SELECT max(rcvid)-1 FROM rcvfrom");
@@ -366,11 +375,11 @@
366 " WHERE name='hooks' AND json_valid(value)",
367 id, id
368 );
369 while( db_step(&q)==SQLITE_ROW ){
370 const char *zCmd = db_column_text(&q,0);
371 char *zCmd2 = hook_subst(zCmd);
372 int needOut = db_column_int(&q,1);
373 Blob out;
374 blob_init(&out,0,0);
375 if( needOut ) hook_changes(&out, zOrigRcvid, zNewRcvid);
376 if( bDryRun ){
@@ -432,11 +441,11 @@
432 char *zCmd;
433 FILE *f;
434 if( cnt==0 ){
435 hook_changes(&chng, zLastRcvid, 0);
436 }
437 zCmd = hook_subst(db_column_text(&q,0));
438 f = popen(zCmd, "w");
439 if( f ){
440 fwrite(blob_buffer(&chng),1,blob_size(&chng),f);
441 pclose(f);
442 }
@@ -448,5 +457,42 @@
448 blob_reset(&chng);
449 hook_backoffice_done:
450 db_commit_transaction();
451 return cnt;
452 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
453
--- src/hook.c
+++ src/hook.c
@@ -79,15 +79,19 @@
79 ** Translate a hook command string into its executable format by
80 ** converting every %-substitutions as follows:
81 **
82 ** %F -> Name of the fossil executable
83 ** %R -> Name of the repository
84 ** %A -> Auxiliary information filename (might be empty string)
85 **
86 ** The returned string is obtained from fossil_malloc() and should
87 ** be freed by the caller.
88 */
89 static char *hook_subst(
90 const char *zCmd,
91 const char *zAuxFilename /* Name of auxiliary information file */
92 ){
93 Blob r;
94 int i;
95 blob_init(&r, 0, 0);
96 while( zCmd[0] ){
97 for(i=0; zCmd[i] && zCmd[i]!='%'; i++){}
@@ -97,10 +101,13 @@
101 blob_append(&r, g.nameOfExe, -1);
102 zCmd += i+2;
103 }else if( zCmd[i+1]=='R' ){
104 blob_append(&r, g.zRepositoryName, -1);
105 zCmd += i+2;
106 }else if( zCmd[i+1]=='A' ){
107 if( zAuxFilename ) blob_append(&r, zAuxFilename, -1);
108 zCmd += i+2;
109 }else{
110 blob_append(&r, zCmd+i, 1);
111 zCmd += i+1;
112 }
113 }
@@ -206,13 +213,14 @@
213 ** > fossil hook test [OPTIONS] ID
214 **
215 ** Run the hook script given by ID for testing purposes.
216 ** Options:
217 **
218 ** --dry-run Print the script on stdout rather than run it
219 ** --base-rcvid N Pretend that the hook-last-rcvid value is N
220 ** --new-rcvid M Pretend that the last rcvid valud is M
221 ** --aux-file NAME NAME is substituted for %A in the script
222 **
223 ** The --base-rcvid and --new-rcvid options are silently ignored if
224 ** the hook type is not "after-receive". The default values for
225 ** --base-rcvid and --new-rcvid cause the last recieve to be processed.
226 */
@@ -351,10 +359,11 @@
359 if( strncmp(zCmd, "test", nCmd)==0 ){
360 Stmt q;
361 int bDryRun = find_option("dry-run", "n", 0)!=0;
362 const char *zOrigRcvid = find_option("base-rcvid",0,1);
363 const char *zNewRcvid = find_option("new-rcvid",0,1);
364 const char *zAuxFilename = find_option("aux-file",0,1);
365 verify_all_options();
366 if( g.argc<4 ) usage("test ID");
367 int id = atoi(g.argv[3]);
368 if( zOrigRcvid==0 ){
369 zOrigRcvid = db_text(0, "SELECT max(rcvid)-1 FROM rcvfrom");
@@ -366,11 +375,11 @@
375 " WHERE name='hooks' AND json_valid(value)",
376 id, id
377 );
378 while( db_step(&q)==SQLITE_ROW ){
379 const char *zCmd = db_column_text(&q,0);
380 char *zCmd2 = hook_subst(zCmd, zAuxFilename);
381 int needOut = db_column_int(&q,1);
382 Blob out;
383 blob_init(&out,0,0);
384 if( needOut ) hook_changes(&out, zOrigRcvid, zNewRcvid);
385 if( bDryRun ){
@@ -432,11 +441,11 @@
441 char *zCmd;
442 FILE *f;
443 if( cnt==0 ){
444 hook_changes(&chng, zLastRcvid, 0);
445 }
446 zCmd = hook_subst(db_column_text(&q,0), 0);
447 f = popen(zCmd, "w");
448 if( f ){
449 fwrite(blob_buffer(&chng),1,blob_size(&chng),f);
450 pclose(f);
451 }
@@ -448,5 +457,42 @@
457 blob_reset(&chng);
458 hook_backoffice_done:
459 db_commit_transaction();
460 return cnt;
461 }
462
463 /*
464 ** Run all hooks of type zType. Use zAuxFile as the auxiliary information
465 ** file.
466 **
467 ** If any hook returns non-zero, then stop running and return non-zero.
468 ** Return zero only if all hooks return zero.
469 */
470 int hook_run(const char *zType, const char *zAuxFile, int traceFlag){
471 Stmt q;
472 int rc = 0;
473 if( !db_exists("SELECT 1 FROM config WHERE name='hooks'") ){
474 return 0;
475 }
476 db_prepare(&q,
477 "SELECT json_extract(jx.value,'$.cmd') "
478 " FROM config, json_each(config.value) AS jx"
479 " WHERE config.name='hooks' AND json_valid(config.value)"
480 " AND json_extract(jx.value,'$.type')=%Q"
481 " ORDER BY json_extract(jx.value,'$.seq');",
482 zType
483 );
484 while( db_step(&q)==SQLITE_ROW ){
485 char *zCmd;
486 zCmd = hook_subst(db_column_text(&q,0), zAuxFile);
487 if( traceFlag ){
488 fossil_print("%s hook: %s\n", zType, zCmd);
489 }
490 rc = fossil_system(zCmd);
491 fossil_free(zCmd);
492 if( rc ){
493 break;
494 }
495 }
496 db_finalize(&q);
497 return rc;
498 }
499

Keyboard Shortcuts

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