|
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 |
} |