Fossil SCM
Added tag/branch option to /json/timeline/ci, analog to HTML mode t/r options.
Commit
762128512afdb2326dc25d16d1431386e397552a
Parent
4db6d7c5cee08a3…
1 file changed
+135
-27
+135
-27
| --- src/json_timeline.c | ||
| +++ src/json_timeline.c | ||
| @@ -27,20 +27,24 @@ | ||
| 27 | 27 | static cson_value * json_timeline_ticket(); |
| 28 | 28 | /* |
| 29 | 29 | ** Mapping of /json/timeline/XXX commands/paths to callbacks. |
| 30 | 30 | */ |
| 31 | 31 | static const JsonPageDef JsonPageDefs_Timeline[] = { |
| 32 | -{"c", json_timeline_ci, 0}, | |
| 32 | +/* the short forms are only enabled in CLI mode, to avoid | |
| 33 | + that we end up with HTTP clients using 3 different names | |
| 34 | + for the same requests. | |
| 35 | +*/ | |
| 36 | +{"c", json_timeline_ci, -1}, | |
| 33 | 37 | {"checkin", json_timeline_ci, 0}, |
| 34 | -{"ci", json_timeline_ci, 0}, | |
| 35 | -{"com", json_timeline_ci, 0}, | |
| 38 | +{"ci", json_timeline_ci, -1}, | |
| 39 | +{"com", json_timeline_ci, -1}, | |
| 36 | 40 | {"commit", json_timeline_ci, 0}, |
| 37 | -{"t", json_timeline_ticket, 0}, | |
| 41 | +{"t", json_timeline_ticket, -1}, | |
| 42 | +{"tkt", json_timeline_ticket, -1}, | |
| 38 | 43 | {"ticket", json_timeline_ticket, 0}, |
| 39 | -{"w", json_timeline_wiki, 0}, | |
| 40 | -{"wi", json_timeline_wiki, 0}, | |
| 41 | -{"wik", json_timeline_wiki, 0}, | |
| 44 | +{"w", json_timeline_wiki, -1}, | |
| 45 | +{"wi", json_timeline_wiki, -1}, | |
| 42 | 46 | {"wiki", json_timeline_wiki, 0}, |
| 43 | 47 | /* Last entry MUST have a NULL name. */ |
| 44 | 48 | {NULL,NULL,0} |
| 45 | 49 | }; |
| 46 | 50 | |
| @@ -106,10 +110,88 @@ | ||
| 106 | 110 | @ WHERE blob.rid=event.objid |
| 107 | 111 | ; |
| 108 | 112 | return zBaseSql; |
| 109 | 113 | } |
| 110 | 114 | |
| 115 | +/* | |
| 116 | +** Internal helper to append query information if the | |
| 117 | +** "tag" or "branch" request properties (CLI: --tag/--branch) | |
| 118 | +** are set. Limits the query to a particular branch/tag. | |
| 119 | +** | |
| 120 | +** tag works like HTML mode's "t" option and branch works like HTML | |
| 121 | +** mode's "r" option. They are very similar, but subtly different - | |
| 122 | +** tag mode shows only entries with a given tag but branch mode can | |
| 123 | +** also reveal some with "related" tags (meaning they were merged into | |
| 124 | +** the requested branch). | |
| 125 | +** | |
| 126 | +** pSql is the target blob to append the query [subset] | |
| 127 | +** to. | |
| 128 | +** | |
| 129 | +** Returns a positive value if it modifies pSql, 0 if it | |
| 130 | +** does not. It returns a negative value if the tag | |
| 131 | +** provided to the request was not found (pSql is not modified | |
| 132 | +** in that case. | |
| 133 | +** | |
| 134 | +** If payload is not NULL then on success its "tag" or "branch" | |
| 135 | +** property is set to the tag/branch name found in the request. | |
| 136 | +** | |
| 137 | +** Only one of "tag" or "branch" modes will work at a time, and if | |
| 138 | +** both are specified, which one takes precedence is unspecified. | |
| 139 | +*/ | |
| 140 | +static char json_timeline_add_tag_branch_clause(Blob *pSql, | |
| 141 | + cson_object * pPayload){ | |
| 142 | + char const * zTag = NULL; | |
| 143 | + char const * zBranch = NULL; | |
| 144 | + int tagid = 0; | |
| 145 | + if(! g.perm.Read ){ | |
| 146 | + return 0; | |
| 147 | + } | |
| 148 | + else if(g.isHTTP){ | |
| 149 | + zTag = json_getenv_cstr("tag"); | |
| 150 | + }else{ | |
| 151 | + zTag = find_option("tag",NULL,1); | |
| 152 | + } | |
| 153 | + if(!zTag || !*zTag){ | |
| 154 | + if(g.isHTTP){ | |
| 155 | + zBranch = json_getenv_cstr("branch"); | |
| 156 | + }else{ | |
| 157 | + zBranch = find_option("branch",NULL,1); | |
| 158 | + } | |
| 159 | + if(!zBranch || !*zBranch){ | |
| 160 | + return 0; | |
| 161 | + } | |
| 162 | + zTag = zBranch; | |
| 163 | + } | |
| 164 | + tagid = db_int(0, "SELECT tagid FROM tag WHERE tagname='sym-%q'", | |
| 165 | + zTag); | |
| 166 | + if(tagid<=0){ | |
| 167 | + return -1; | |
| 168 | + } | |
| 169 | + if(pPayload){ | |
| 170 | + cson_object_set( pPayload, zBranch ? "branch" : "tag", json_new_string(zTag) ); | |
| 171 | + } | |
| 172 | + blob_appendf(pSql, | |
| 173 | + " AND (" | |
| 174 | + " EXISTS(SELECT 1 FROM tagxref" | |
| 175 | + " WHERE tagid=%d AND tagtype>0 AND rid=blob.rid)", | |
| 176 | + tagid); | |
| 177 | + if(zBranch){ | |
| 178 | + /* from "r" flag code in page_timeline().*/ | |
| 179 | + blob_appendf(pSql, | |
| 180 | + " OR EXISTS(SELECT 1 FROM plink JOIN tagxref ON rid=cid" | |
| 181 | + " WHERE tagid=%d AND tagtype>0 AND pid=blob.rid)", | |
| 182 | + tagid); | |
| 183 | +#if 0 /* from the undocumented "mionly" flag in page_timeline() */ | |
| 184 | + blob_appendf(pSql, | |
| 185 | + " OR EXISTS(SELECT 1 FROM plink JOIN tagxref ON rid=pid" | |
| 186 | + " WHERE tagid=%d AND tagtype>0 AND cid=blob.rid)", | |
| 187 | + tagid); | |
| 188 | +#endif | |
| 189 | + } | |
| 190 | + blob_append(pSql," ) ",3); | |
| 191 | + return 1; | |
| 192 | +} | |
| 111 | 193 | /* |
| 112 | 194 | ** Helper for the timeline family of functions. Possibly appends 1 |
| 113 | 195 | ** AND clause and an ORDER BY clause to pSql, depending on the state |
| 114 | 196 | ** of the "after" ("a") or "before" ("b") environment parameters. |
| 115 | 197 | ** This function gives "after" precedence over "before", and only |
| @@ -188,31 +270,39 @@ | ||
| 188 | 270 | ** Internal helper for the json_timeline_EVENTTYPE() family of |
| 189 | 271 | ** functions. zEventType must be one of (ci, w, t). pSql must be a |
| 190 | 272 | ** cleanly-initialized, empty Blob to store the sql in. If pPayload is |
| 191 | 273 | ** not NULL it is assumed to be the pending response payload. If |
| 192 | 274 | ** json_timeline_limit() returns non-0, this function adds a LIMIT |
| 193 | -** clause to the generated SQL and (if pPayload is not NULL) adds the | |
| 194 | -** limit value as the "limit" property of pPayload. | |
| 275 | +** clause to the generated SQL. | |
| 276 | +** | |
| 277 | +** If pPayload is not NULL then this might add properties to pPayload, | |
| 278 | +** reflecting options set in the request environment. | |
| 279 | +** | |
| 280 | +** Returns 0 on success. On error processing should not continue and | |
| 281 | +** the returned value should be used as g.json.resultCode. | |
| 195 | 282 | */ |
| 196 | -static void json_timeline_setup_sql( char const * zEventType, | |
| 197 | - Blob * pSql, | |
| 198 | - cson_object * pPayload ){ | |
| 283 | +static int json_timeline_setup_sql( char const * zEventType, | |
| 284 | + Blob * pSql, | |
| 285 | + cson_object * pPayload ){ | |
| 199 | 286 | int limit; |
| 200 | 287 | assert( zEventType && *zEventType && pSql ); |
| 201 | 288 | json_timeline_temp_table(); |
| 202 | 289 | blob_append(pSql, "INSERT OR IGNORE INTO json_timeline ", -1); |
| 203 | 290 | blob_append(pSql, json_timeline_query(), -1 ); |
| 204 | 291 | blob_appendf(pSql, " AND event.type IN(%Q) ", zEventType); |
| 292 | + if( json_timeline_add_tag_branch_clause(pSql, pPayload) < 0 ){ | |
| 293 | + return FSL_JSON_E_INVALID_ARGS; | |
| 294 | + } | |
| 205 | 295 | json_timeline_add_time_clause(pSql); |
| 206 | 296 | limit = json_timeline_limit(); |
| 207 | - if(limit){ | |
| 297 | + if(limit>=0){ | |
| 208 | 298 | blob_appendf(pSql,"LIMIT %d ",limit); |
| 209 | 299 | } |
| 210 | 300 | if(pPayload){ |
| 211 | - cson_object_set(pPayload, "limit",json_new_int(limit)); | |
| 301 | + cson_object_set(pPayload, "limit", json_new_int(limit)); | |
| 212 | 302 | } |
| 213 | - | |
| 303 | + return 0; | |
| 214 | 304 | } |
| 215 | 305 | |
| 216 | 306 | /* |
| 217 | 307 | ** If any files are associated with the given rid, a JSON array |
| 218 | 308 | ** containing information about them is returned (and is owned by the |
| @@ -219,11 +309,11 @@ | ||
| 219 | 309 | ** caller). If no files are associated with it then NULL is returned. |
| 220 | 310 | */ |
| 221 | 311 | cson_value * json_get_changed_files(int rid){ |
| 222 | 312 | cson_value * rowsV = NULL; |
| 223 | 313 | cson_array * rows = NULL; |
| 224 | - Stmt q; | |
| 314 | + Stmt q = empty_Stmt; | |
| 225 | 315 | db_prepare(&q, |
| 226 | 316 | #if 0 |
| 227 | 317 | "SELECT (mlink.pid==0) AS isNew," |
| 228 | 318 | " (mlink.fid==0) AS isDel," |
| 229 | 319 | " filename.name AS name" |
| @@ -238,11 +328,11 @@ | ||
| 238 | 328 | " (SELECT name FROM filename WHERE fnid=mlink.fnid) AS name," |
| 239 | 329 | " (SELECT uuid FROM blob WHERE rid=fid) as uuid," |
| 240 | 330 | " (SELECT uuid FROM blob WHERE rid=pid) as prevUuid" |
| 241 | 331 | " FROM mlink" |
| 242 | 332 | " WHERE mid=%d AND pid!=fid" |
| 243 | - " ORDER BY 3 /*sort*/", | |
| 333 | + " ORDER BY name /*sort*/", | |
| 244 | 334 | #endif |
| 245 | 335 | rid |
| 246 | 336 | ); |
| 247 | 337 | while( (SQLITE_ROW == db_step(&q)) ){ |
| 248 | 338 | cson_value * rowV = cson_value_new_object(); |
| @@ -281,11 +371,11 @@ | ||
| 281 | 371 | cson_value * tmp = NULL; |
| 282 | 372 | cson_value * listV = NULL; |
| 283 | 373 | cson_array * list = NULL; |
| 284 | 374 | int check = 0; |
| 285 | 375 | int showFiles = 0; |
| 286 | - Stmt q; | |
| 376 | + Stmt q = empty_Stmt; | |
| 287 | 377 | char warnRowToJsonFailed = 0; |
| 288 | 378 | char warnStringToArrayFailed = 0; |
| 289 | 379 | Blob sql = empty_blob; |
| 290 | 380 | if( !g.perm.Read/* && !g.perm.RdTkt && !g.perm.RdWiki*/ ){ |
| 291 | 381 | g.json.resultCode = FSL_JSON_E_DENIED; |
| @@ -296,11 +386,15 @@ | ||
| 296 | 386 | }else{ |
| 297 | 387 | showFiles = 0!=find_option("show-files", "f",0); |
| 298 | 388 | } |
| 299 | 389 | payV = cson_value_new_object(); |
| 300 | 390 | pay = cson_value_get_object(payV); |
| 301 | - json_timeline_setup_sql( "ci", &sql, pay ); | |
| 391 | + check = json_timeline_setup_sql( "ci", &sql, pay ); | |
| 392 | + if(check){ | |
| 393 | + g.json.resultCode = check; | |
| 394 | + goto error; | |
| 395 | + } | |
| 302 | 396 | #define SET(K) if(0!=(check=cson_object_set(pay,K,tmp))){ \ |
| 303 | 397 | g.json.resultCode = (cson_rc.AllocError==check) \ |
| 304 | 398 | ? FSL_JSON_E_ALLOC : FSL_JSON_E_UNKNOWN; \ |
| 305 | 399 | goto error;\ |
| 306 | 400 | } |
| @@ -310,25 +404,27 @@ | ||
| 310 | 404 | SET("timelineSql"); |
| 311 | 405 | #endif |
| 312 | 406 | db_multi_exec(blob_buffer(&sql)); |
| 313 | 407 | blob_reset(&sql); |
| 314 | 408 | db_prepare(&q, "SELECT " |
| 315 | - " rid AS rid," | |
| 409 | + " rid AS rid" | |
| 410 | +#if 0 | |
| 316 | 411 | " uuid AS uuid," |
| 317 | 412 | " mtime AS timestamp," |
| 318 | -#if 0 | |
| 413 | +# if 0 | |
| 319 | 414 | " timestampString AS timestampString," |
| 320 | -#endif | |
| 415 | +# endif | |
| 321 | 416 | " comment AS comment, " |
| 322 | 417 | " user AS user," |
| 323 | 418 | " isLeaf AS isLeaf," /*FIXME: convert to JSON bool */ |
| 324 | 419 | " bgColor AS bgColor," /* why always null? */ |
| 325 | 420 | " eventType AS eventType" |
| 326 | -#if 0 | |
| 421 | +# if 0 | |
| 327 | 422 | " tags AS tags" |
| 328 | 423 | /*tagId is always null?*/ |
| 329 | 424 | " tagId AS tagId" |
| 425 | +# endif | |
| 330 | 426 | #endif |
| 331 | 427 | " FROM json_timeline" |
| 332 | 428 | " ORDER BY sortId"); |
| 333 | 429 | listV = cson_value_new_array(); |
| 334 | 430 | list = cson_value_get_array(listV); |
| @@ -370,19 +466,24 @@ | ||
| 370 | 466 | cson_object * pay = NULL; |
| 371 | 467 | cson_value * tmp = NULL; |
| 372 | 468 | cson_value * listV = NULL; |
| 373 | 469 | cson_array * list = NULL; |
| 374 | 470 | int check = 0; |
| 375 | - Stmt q; | |
| 471 | + Stmt q = empty_Stmt; | |
| 376 | 472 | Blob sql = empty_blob; |
| 377 | 473 | if( !g.perm.Read || !g.perm.RdWiki ){ |
| 378 | 474 | g.json.resultCode = FSL_JSON_E_DENIED; |
| 379 | 475 | return NULL; |
| 380 | 476 | } |
| 381 | 477 | payV = cson_value_new_object(); |
| 382 | 478 | pay = cson_value_get_object(payV); |
| 383 | - json_timeline_setup_sql( "w", &sql, pay ); | |
| 479 | + check = json_timeline_setup_sql( "w", &sql, pay ); | |
| 480 | + if(check){ | |
| 481 | + g.json.resultCode = check; | |
| 482 | + goto error; | |
| 483 | + } | |
| 484 | + | |
| 384 | 485 | #define SET(K) if(0!=(check=cson_object_set(pay,K,tmp))){ \ |
| 385 | 486 | g.json.resultCode = (cson_rc.AllocError==check) \ |
| 386 | 487 | ? FSL_JSON_E_ALLOC : FSL_JSON_E_UNKNOWN; \ |
| 387 | 488 | goto error;\ |
| 388 | 489 | } |
| @@ -422,10 +523,11 @@ | ||
| 422 | 523 | assert( 0 != g.json.resultCode ); |
| 423 | 524 | cson_value_free(payV); |
| 424 | 525 | payV = NULL; |
| 425 | 526 | ok: |
| 426 | 527 | db_finalize(&q); |
| 528 | + blob_reset(&sql); | |
| 427 | 529 | return payV; |
| 428 | 530 | } |
| 429 | 531 | |
| 430 | 532 | /* |
| 431 | 533 | ** Implementation of /json/timeline/ticket. |
| @@ -437,19 +539,24 @@ | ||
| 437 | 539 | cson_object * pay = NULL; |
| 438 | 540 | cson_value * tmp = NULL; |
| 439 | 541 | cson_value * listV = NULL; |
| 440 | 542 | cson_array * list = NULL; |
| 441 | 543 | int check = 0; |
| 442 | - Stmt q; | |
| 544 | + Stmt q = empty_Stmt; | |
| 443 | 545 | Blob sql = empty_blob; |
| 444 | 546 | if( !g.perm.Read || !g.perm.RdTkt ){ |
| 445 | 547 | g.json.resultCode = FSL_JSON_E_DENIED; |
| 446 | 548 | return NULL; |
| 447 | 549 | } |
| 448 | 550 | payV = cson_value_new_object(); |
| 449 | 551 | pay = cson_value_get_object(payV); |
| 450 | - json_timeline_setup_sql( "t", &sql, pay ); | |
| 552 | + check = json_timeline_setup_sql( "t", &sql, pay ); | |
| 553 | + if(check){ | |
| 554 | + g.json.resultCode = check; | |
| 555 | + goto error; | |
| 556 | + } | |
| 557 | + | |
| 451 | 558 | db_multi_exec(blob_buffer(&sql)); |
| 452 | 559 | #define SET(K) if(0!=(check=cson_object_set(pay,K,tmp))){ \ |
| 453 | 560 | g.json.resultCode = (cson_rc.AllocError==check) \ |
| 454 | 561 | ? FSL_JSON_E_ALLOC : FSL_JSON_E_UNKNOWN; \ |
| 455 | 562 | goto error;\ |
| @@ -514,9 +621,10 @@ | ||
| 514 | 621 | error: |
| 515 | 622 | assert( 0 != g.json.resultCode ); |
| 516 | 623 | cson_value_free(payV); |
| 517 | 624 | payV = NULL; |
| 518 | 625 | ok: |
| 626 | + blob_reset(&sql); | |
| 519 | 627 | db_finalize(&q); |
| 520 | 628 | return payV; |
| 521 | 629 | } |
| 522 | 630 | |
| 523 | 631 |
| --- src/json_timeline.c | |
| +++ src/json_timeline.c | |
| @@ -27,20 +27,24 @@ | |
| 27 | static cson_value * json_timeline_ticket(); |
| 28 | /* |
| 29 | ** Mapping of /json/timeline/XXX commands/paths to callbacks. |
| 30 | */ |
| 31 | static const JsonPageDef JsonPageDefs_Timeline[] = { |
| 32 | {"c", json_timeline_ci, 0}, |
| 33 | {"checkin", json_timeline_ci, 0}, |
| 34 | {"ci", json_timeline_ci, 0}, |
| 35 | {"com", json_timeline_ci, 0}, |
| 36 | {"commit", json_timeline_ci, 0}, |
| 37 | {"t", json_timeline_ticket, 0}, |
| 38 | {"ticket", json_timeline_ticket, 0}, |
| 39 | {"w", json_timeline_wiki, 0}, |
| 40 | {"wi", json_timeline_wiki, 0}, |
| 41 | {"wik", json_timeline_wiki, 0}, |
| 42 | {"wiki", json_timeline_wiki, 0}, |
| 43 | /* Last entry MUST have a NULL name. */ |
| 44 | {NULL,NULL,0} |
| 45 | }; |
| 46 | |
| @@ -106,10 +110,88 @@ | |
| 106 | @ WHERE blob.rid=event.objid |
| 107 | ; |
| 108 | return zBaseSql; |
| 109 | } |
| 110 | |
| 111 | /* |
| 112 | ** Helper for the timeline family of functions. Possibly appends 1 |
| 113 | ** AND clause and an ORDER BY clause to pSql, depending on the state |
| 114 | ** of the "after" ("a") or "before" ("b") environment parameters. |
| 115 | ** This function gives "after" precedence over "before", and only |
| @@ -188,31 +270,39 @@ | |
| 188 | ** Internal helper for the json_timeline_EVENTTYPE() family of |
| 189 | ** functions. zEventType must be one of (ci, w, t). pSql must be a |
| 190 | ** cleanly-initialized, empty Blob to store the sql in. If pPayload is |
| 191 | ** not NULL it is assumed to be the pending response payload. If |
| 192 | ** json_timeline_limit() returns non-0, this function adds a LIMIT |
| 193 | ** clause to the generated SQL and (if pPayload is not NULL) adds the |
| 194 | ** limit value as the "limit" property of pPayload. |
| 195 | */ |
| 196 | static void json_timeline_setup_sql( char const * zEventType, |
| 197 | Blob * pSql, |
| 198 | cson_object * pPayload ){ |
| 199 | int limit; |
| 200 | assert( zEventType && *zEventType && pSql ); |
| 201 | json_timeline_temp_table(); |
| 202 | blob_append(pSql, "INSERT OR IGNORE INTO json_timeline ", -1); |
| 203 | blob_append(pSql, json_timeline_query(), -1 ); |
| 204 | blob_appendf(pSql, " AND event.type IN(%Q) ", zEventType); |
| 205 | json_timeline_add_time_clause(pSql); |
| 206 | limit = json_timeline_limit(); |
| 207 | if(limit){ |
| 208 | blob_appendf(pSql,"LIMIT %d ",limit); |
| 209 | } |
| 210 | if(pPayload){ |
| 211 | cson_object_set(pPayload, "limit",json_new_int(limit)); |
| 212 | } |
| 213 | |
| 214 | } |
| 215 | |
| 216 | /* |
| 217 | ** If any files are associated with the given rid, a JSON array |
| 218 | ** containing information about them is returned (and is owned by the |
| @@ -219,11 +309,11 @@ | |
| 219 | ** caller). If no files are associated with it then NULL is returned. |
| 220 | */ |
| 221 | cson_value * json_get_changed_files(int rid){ |
| 222 | cson_value * rowsV = NULL; |
| 223 | cson_array * rows = NULL; |
| 224 | Stmt q; |
| 225 | db_prepare(&q, |
| 226 | #if 0 |
| 227 | "SELECT (mlink.pid==0) AS isNew," |
| 228 | " (mlink.fid==0) AS isDel," |
| 229 | " filename.name AS name" |
| @@ -238,11 +328,11 @@ | |
| 238 | " (SELECT name FROM filename WHERE fnid=mlink.fnid) AS name," |
| 239 | " (SELECT uuid FROM blob WHERE rid=fid) as uuid," |
| 240 | " (SELECT uuid FROM blob WHERE rid=pid) as prevUuid" |
| 241 | " FROM mlink" |
| 242 | " WHERE mid=%d AND pid!=fid" |
| 243 | " ORDER BY 3 /*sort*/", |
| 244 | #endif |
| 245 | rid |
| 246 | ); |
| 247 | while( (SQLITE_ROW == db_step(&q)) ){ |
| 248 | cson_value * rowV = cson_value_new_object(); |
| @@ -281,11 +371,11 @@ | |
| 281 | cson_value * tmp = NULL; |
| 282 | cson_value * listV = NULL; |
| 283 | cson_array * list = NULL; |
| 284 | int check = 0; |
| 285 | int showFiles = 0; |
| 286 | Stmt q; |
| 287 | char warnRowToJsonFailed = 0; |
| 288 | char warnStringToArrayFailed = 0; |
| 289 | Blob sql = empty_blob; |
| 290 | if( !g.perm.Read/* && !g.perm.RdTkt && !g.perm.RdWiki*/ ){ |
| 291 | g.json.resultCode = FSL_JSON_E_DENIED; |
| @@ -296,11 +386,15 @@ | |
| 296 | }else{ |
| 297 | showFiles = 0!=find_option("show-files", "f",0); |
| 298 | } |
| 299 | payV = cson_value_new_object(); |
| 300 | pay = cson_value_get_object(payV); |
| 301 | json_timeline_setup_sql( "ci", &sql, pay ); |
| 302 | #define SET(K) if(0!=(check=cson_object_set(pay,K,tmp))){ \ |
| 303 | g.json.resultCode = (cson_rc.AllocError==check) \ |
| 304 | ? FSL_JSON_E_ALLOC : FSL_JSON_E_UNKNOWN; \ |
| 305 | goto error;\ |
| 306 | } |
| @@ -310,25 +404,27 @@ | |
| 310 | SET("timelineSql"); |
| 311 | #endif |
| 312 | db_multi_exec(blob_buffer(&sql)); |
| 313 | blob_reset(&sql); |
| 314 | db_prepare(&q, "SELECT " |
| 315 | " rid AS rid," |
| 316 | " uuid AS uuid," |
| 317 | " mtime AS timestamp," |
| 318 | #if 0 |
| 319 | " timestampString AS timestampString," |
| 320 | #endif |
| 321 | " comment AS comment, " |
| 322 | " user AS user," |
| 323 | " isLeaf AS isLeaf," /*FIXME: convert to JSON bool */ |
| 324 | " bgColor AS bgColor," /* why always null? */ |
| 325 | " eventType AS eventType" |
| 326 | #if 0 |
| 327 | " tags AS tags" |
| 328 | /*tagId is always null?*/ |
| 329 | " tagId AS tagId" |
| 330 | #endif |
| 331 | " FROM json_timeline" |
| 332 | " ORDER BY sortId"); |
| 333 | listV = cson_value_new_array(); |
| 334 | list = cson_value_get_array(listV); |
| @@ -370,19 +466,24 @@ | |
| 370 | cson_object * pay = NULL; |
| 371 | cson_value * tmp = NULL; |
| 372 | cson_value * listV = NULL; |
| 373 | cson_array * list = NULL; |
| 374 | int check = 0; |
| 375 | Stmt q; |
| 376 | Blob sql = empty_blob; |
| 377 | if( !g.perm.Read || !g.perm.RdWiki ){ |
| 378 | g.json.resultCode = FSL_JSON_E_DENIED; |
| 379 | return NULL; |
| 380 | } |
| 381 | payV = cson_value_new_object(); |
| 382 | pay = cson_value_get_object(payV); |
| 383 | json_timeline_setup_sql( "w", &sql, pay ); |
| 384 | #define SET(K) if(0!=(check=cson_object_set(pay,K,tmp))){ \ |
| 385 | g.json.resultCode = (cson_rc.AllocError==check) \ |
| 386 | ? FSL_JSON_E_ALLOC : FSL_JSON_E_UNKNOWN; \ |
| 387 | goto error;\ |
| 388 | } |
| @@ -422,10 +523,11 @@ | |
| 422 | assert( 0 != g.json.resultCode ); |
| 423 | cson_value_free(payV); |
| 424 | payV = NULL; |
| 425 | ok: |
| 426 | db_finalize(&q); |
| 427 | return payV; |
| 428 | } |
| 429 | |
| 430 | /* |
| 431 | ** Implementation of /json/timeline/ticket. |
| @@ -437,19 +539,24 @@ | |
| 437 | cson_object * pay = NULL; |
| 438 | cson_value * tmp = NULL; |
| 439 | cson_value * listV = NULL; |
| 440 | cson_array * list = NULL; |
| 441 | int check = 0; |
| 442 | Stmt q; |
| 443 | Blob sql = empty_blob; |
| 444 | if( !g.perm.Read || !g.perm.RdTkt ){ |
| 445 | g.json.resultCode = FSL_JSON_E_DENIED; |
| 446 | return NULL; |
| 447 | } |
| 448 | payV = cson_value_new_object(); |
| 449 | pay = cson_value_get_object(payV); |
| 450 | json_timeline_setup_sql( "t", &sql, pay ); |
| 451 | db_multi_exec(blob_buffer(&sql)); |
| 452 | #define SET(K) if(0!=(check=cson_object_set(pay,K,tmp))){ \ |
| 453 | g.json.resultCode = (cson_rc.AllocError==check) \ |
| 454 | ? FSL_JSON_E_ALLOC : FSL_JSON_E_UNKNOWN; \ |
| 455 | goto error;\ |
| @@ -514,9 +621,10 @@ | |
| 514 | error: |
| 515 | assert( 0 != g.json.resultCode ); |
| 516 | cson_value_free(payV); |
| 517 | payV = NULL; |
| 518 | ok: |
| 519 | db_finalize(&q); |
| 520 | return payV; |
| 521 | } |
| 522 | |
| 523 |
| --- src/json_timeline.c | |
| +++ src/json_timeline.c | |
| @@ -27,20 +27,24 @@ | |
| 27 | static cson_value * json_timeline_ticket(); |
| 28 | /* |
| 29 | ** Mapping of /json/timeline/XXX commands/paths to callbacks. |
| 30 | */ |
| 31 | static const JsonPageDef JsonPageDefs_Timeline[] = { |
| 32 | /* the short forms are only enabled in CLI mode, to avoid |
| 33 | that we end up with HTTP clients using 3 different names |
| 34 | for the same requests. |
| 35 | */ |
| 36 | {"c", json_timeline_ci, -1}, |
| 37 | {"checkin", json_timeline_ci, 0}, |
| 38 | {"ci", json_timeline_ci, -1}, |
| 39 | {"com", json_timeline_ci, -1}, |
| 40 | {"commit", json_timeline_ci, 0}, |
| 41 | {"t", json_timeline_ticket, -1}, |
| 42 | {"tkt", json_timeline_ticket, -1}, |
| 43 | {"ticket", json_timeline_ticket, 0}, |
| 44 | {"w", json_timeline_wiki, -1}, |
| 45 | {"wi", json_timeline_wiki, -1}, |
| 46 | {"wiki", json_timeline_wiki, 0}, |
| 47 | /* Last entry MUST have a NULL name. */ |
| 48 | {NULL,NULL,0} |
| 49 | }; |
| 50 | |
| @@ -106,10 +110,88 @@ | |
| 110 | @ WHERE blob.rid=event.objid |
| 111 | ; |
| 112 | return zBaseSql; |
| 113 | } |
| 114 | |
| 115 | /* |
| 116 | ** Internal helper to append query information if the |
| 117 | ** "tag" or "branch" request properties (CLI: --tag/--branch) |
| 118 | ** are set. Limits the query to a particular branch/tag. |
| 119 | ** |
| 120 | ** tag works like HTML mode's "t" option and branch works like HTML |
| 121 | ** mode's "r" option. They are very similar, but subtly different - |
| 122 | ** tag mode shows only entries with a given tag but branch mode can |
| 123 | ** also reveal some with "related" tags (meaning they were merged into |
| 124 | ** the requested branch). |
| 125 | ** |
| 126 | ** pSql is the target blob to append the query [subset] |
| 127 | ** to. |
| 128 | ** |
| 129 | ** Returns a positive value if it modifies pSql, 0 if it |
| 130 | ** does not. It returns a negative value if the tag |
| 131 | ** provided to the request was not found (pSql is not modified |
| 132 | ** in that case. |
| 133 | ** |
| 134 | ** If payload is not NULL then on success its "tag" or "branch" |
| 135 | ** property is set to the tag/branch name found in the request. |
| 136 | ** |
| 137 | ** Only one of "tag" or "branch" modes will work at a time, and if |
| 138 | ** both are specified, which one takes precedence is unspecified. |
| 139 | */ |
| 140 | static char json_timeline_add_tag_branch_clause(Blob *pSql, |
| 141 | cson_object * pPayload){ |
| 142 | char const * zTag = NULL; |
| 143 | char const * zBranch = NULL; |
| 144 | int tagid = 0; |
| 145 | if(! g.perm.Read ){ |
| 146 | return 0; |
| 147 | } |
| 148 | else if(g.isHTTP){ |
| 149 | zTag = json_getenv_cstr("tag"); |
| 150 | }else{ |
| 151 | zTag = find_option("tag",NULL,1); |
| 152 | } |
| 153 | if(!zTag || !*zTag){ |
| 154 | if(g.isHTTP){ |
| 155 | zBranch = json_getenv_cstr("branch"); |
| 156 | }else{ |
| 157 | zBranch = find_option("branch",NULL,1); |
| 158 | } |
| 159 | if(!zBranch || !*zBranch){ |
| 160 | return 0; |
| 161 | } |
| 162 | zTag = zBranch; |
| 163 | } |
| 164 | tagid = db_int(0, "SELECT tagid FROM tag WHERE tagname='sym-%q'", |
| 165 | zTag); |
| 166 | if(tagid<=0){ |
| 167 | return -1; |
| 168 | } |
| 169 | if(pPayload){ |
| 170 | cson_object_set( pPayload, zBranch ? "branch" : "tag", json_new_string(zTag) ); |
| 171 | } |
| 172 | blob_appendf(pSql, |
| 173 | " AND (" |
| 174 | " EXISTS(SELECT 1 FROM tagxref" |
| 175 | " WHERE tagid=%d AND tagtype>0 AND rid=blob.rid)", |
| 176 | tagid); |
| 177 | if(zBranch){ |
| 178 | /* from "r" flag code in page_timeline().*/ |
| 179 | blob_appendf(pSql, |
| 180 | " OR EXISTS(SELECT 1 FROM plink JOIN tagxref ON rid=cid" |
| 181 | " WHERE tagid=%d AND tagtype>0 AND pid=blob.rid)", |
| 182 | tagid); |
| 183 | #if 0 /* from the undocumented "mionly" flag in page_timeline() */ |
| 184 | blob_appendf(pSql, |
| 185 | " OR EXISTS(SELECT 1 FROM plink JOIN tagxref ON rid=pid" |
| 186 | " WHERE tagid=%d AND tagtype>0 AND cid=blob.rid)", |
| 187 | tagid); |
| 188 | #endif |
| 189 | } |
| 190 | blob_append(pSql," ) ",3); |
| 191 | return 1; |
| 192 | } |
| 193 | /* |
| 194 | ** Helper for the timeline family of functions. Possibly appends 1 |
| 195 | ** AND clause and an ORDER BY clause to pSql, depending on the state |
| 196 | ** of the "after" ("a") or "before" ("b") environment parameters. |
| 197 | ** This function gives "after" precedence over "before", and only |
| @@ -188,31 +270,39 @@ | |
| 270 | ** Internal helper for the json_timeline_EVENTTYPE() family of |
| 271 | ** functions. zEventType must be one of (ci, w, t). pSql must be a |
| 272 | ** cleanly-initialized, empty Blob to store the sql in. If pPayload is |
| 273 | ** not NULL it is assumed to be the pending response payload. If |
| 274 | ** json_timeline_limit() returns non-0, this function adds a LIMIT |
| 275 | ** clause to the generated SQL. |
| 276 | ** |
| 277 | ** If pPayload is not NULL then this might add properties to pPayload, |
| 278 | ** reflecting options set in the request environment. |
| 279 | ** |
| 280 | ** Returns 0 on success. On error processing should not continue and |
| 281 | ** the returned value should be used as g.json.resultCode. |
| 282 | */ |
| 283 | static int json_timeline_setup_sql( char const * zEventType, |
| 284 | Blob * pSql, |
| 285 | cson_object * pPayload ){ |
| 286 | int limit; |
| 287 | assert( zEventType && *zEventType && pSql ); |
| 288 | json_timeline_temp_table(); |
| 289 | blob_append(pSql, "INSERT OR IGNORE INTO json_timeline ", -1); |
| 290 | blob_append(pSql, json_timeline_query(), -1 ); |
| 291 | blob_appendf(pSql, " AND event.type IN(%Q) ", zEventType); |
| 292 | if( json_timeline_add_tag_branch_clause(pSql, pPayload) < 0 ){ |
| 293 | return FSL_JSON_E_INVALID_ARGS; |
| 294 | } |
| 295 | json_timeline_add_time_clause(pSql); |
| 296 | limit = json_timeline_limit(); |
| 297 | if(limit>=0){ |
| 298 | blob_appendf(pSql,"LIMIT %d ",limit); |
| 299 | } |
| 300 | if(pPayload){ |
| 301 | cson_object_set(pPayload, "limit", json_new_int(limit)); |
| 302 | } |
| 303 | return 0; |
| 304 | } |
| 305 | |
| 306 | /* |
| 307 | ** If any files are associated with the given rid, a JSON array |
| 308 | ** containing information about them is returned (and is owned by the |
| @@ -219,11 +309,11 @@ | |
| 309 | ** caller). If no files are associated with it then NULL is returned. |
| 310 | */ |
| 311 | cson_value * json_get_changed_files(int rid){ |
| 312 | cson_value * rowsV = NULL; |
| 313 | cson_array * rows = NULL; |
| 314 | Stmt q = empty_Stmt; |
| 315 | db_prepare(&q, |
| 316 | #if 0 |
| 317 | "SELECT (mlink.pid==0) AS isNew," |
| 318 | " (mlink.fid==0) AS isDel," |
| 319 | " filename.name AS name" |
| @@ -238,11 +328,11 @@ | |
| 328 | " (SELECT name FROM filename WHERE fnid=mlink.fnid) AS name," |
| 329 | " (SELECT uuid FROM blob WHERE rid=fid) as uuid," |
| 330 | " (SELECT uuid FROM blob WHERE rid=pid) as prevUuid" |
| 331 | " FROM mlink" |
| 332 | " WHERE mid=%d AND pid!=fid" |
| 333 | " ORDER BY name /*sort*/", |
| 334 | #endif |
| 335 | rid |
| 336 | ); |
| 337 | while( (SQLITE_ROW == db_step(&q)) ){ |
| 338 | cson_value * rowV = cson_value_new_object(); |
| @@ -281,11 +371,11 @@ | |
| 371 | cson_value * tmp = NULL; |
| 372 | cson_value * listV = NULL; |
| 373 | cson_array * list = NULL; |
| 374 | int check = 0; |
| 375 | int showFiles = 0; |
| 376 | Stmt q = empty_Stmt; |
| 377 | char warnRowToJsonFailed = 0; |
| 378 | char warnStringToArrayFailed = 0; |
| 379 | Blob sql = empty_blob; |
| 380 | if( !g.perm.Read/* && !g.perm.RdTkt && !g.perm.RdWiki*/ ){ |
| 381 | g.json.resultCode = FSL_JSON_E_DENIED; |
| @@ -296,11 +386,15 @@ | |
| 386 | }else{ |
| 387 | showFiles = 0!=find_option("show-files", "f",0); |
| 388 | } |
| 389 | payV = cson_value_new_object(); |
| 390 | pay = cson_value_get_object(payV); |
| 391 | check = json_timeline_setup_sql( "ci", &sql, pay ); |
| 392 | if(check){ |
| 393 | g.json.resultCode = check; |
| 394 | goto error; |
| 395 | } |
| 396 | #define SET(K) if(0!=(check=cson_object_set(pay,K,tmp))){ \ |
| 397 | g.json.resultCode = (cson_rc.AllocError==check) \ |
| 398 | ? FSL_JSON_E_ALLOC : FSL_JSON_E_UNKNOWN; \ |
| 399 | goto error;\ |
| 400 | } |
| @@ -310,25 +404,27 @@ | |
| 404 | SET("timelineSql"); |
| 405 | #endif |
| 406 | db_multi_exec(blob_buffer(&sql)); |
| 407 | blob_reset(&sql); |
| 408 | db_prepare(&q, "SELECT " |
| 409 | " rid AS rid" |
| 410 | #if 0 |
| 411 | " uuid AS uuid," |
| 412 | " mtime AS timestamp," |
| 413 | # if 0 |
| 414 | " timestampString AS timestampString," |
| 415 | # endif |
| 416 | " comment AS comment, " |
| 417 | " user AS user," |
| 418 | " isLeaf AS isLeaf," /*FIXME: convert to JSON bool */ |
| 419 | " bgColor AS bgColor," /* why always null? */ |
| 420 | " eventType AS eventType" |
| 421 | # if 0 |
| 422 | " tags AS tags" |
| 423 | /*tagId is always null?*/ |
| 424 | " tagId AS tagId" |
| 425 | # endif |
| 426 | #endif |
| 427 | " FROM json_timeline" |
| 428 | " ORDER BY sortId"); |
| 429 | listV = cson_value_new_array(); |
| 430 | list = cson_value_get_array(listV); |
| @@ -370,19 +466,24 @@ | |
| 466 | cson_object * pay = NULL; |
| 467 | cson_value * tmp = NULL; |
| 468 | cson_value * listV = NULL; |
| 469 | cson_array * list = NULL; |
| 470 | int check = 0; |
| 471 | Stmt q = empty_Stmt; |
| 472 | Blob sql = empty_blob; |
| 473 | if( !g.perm.Read || !g.perm.RdWiki ){ |
| 474 | g.json.resultCode = FSL_JSON_E_DENIED; |
| 475 | return NULL; |
| 476 | } |
| 477 | payV = cson_value_new_object(); |
| 478 | pay = cson_value_get_object(payV); |
| 479 | check = json_timeline_setup_sql( "w", &sql, pay ); |
| 480 | if(check){ |
| 481 | g.json.resultCode = check; |
| 482 | goto error; |
| 483 | } |
| 484 | |
| 485 | #define SET(K) if(0!=(check=cson_object_set(pay,K,tmp))){ \ |
| 486 | g.json.resultCode = (cson_rc.AllocError==check) \ |
| 487 | ? FSL_JSON_E_ALLOC : FSL_JSON_E_UNKNOWN; \ |
| 488 | goto error;\ |
| 489 | } |
| @@ -422,10 +523,11 @@ | |
| 523 | assert( 0 != g.json.resultCode ); |
| 524 | cson_value_free(payV); |
| 525 | payV = NULL; |
| 526 | ok: |
| 527 | db_finalize(&q); |
| 528 | blob_reset(&sql); |
| 529 | return payV; |
| 530 | } |
| 531 | |
| 532 | /* |
| 533 | ** Implementation of /json/timeline/ticket. |
| @@ -437,19 +539,24 @@ | |
| 539 | cson_object * pay = NULL; |
| 540 | cson_value * tmp = NULL; |
| 541 | cson_value * listV = NULL; |
| 542 | cson_array * list = NULL; |
| 543 | int check = 0; |
| 544 | Stmt q = empty_Stmt; |
| 545 | Blob sql = empty_blob; |
| 546 | if( !g.perm.Read || !g.perm.RdTkt ){ |
| 547 | g.json.resultCode = FSL_JSON_E_DENIED; |
| 548 | return NULL; |
| 549 | } |
| 550 | payV = cson_value_new_object(); |
| 551 | pay = cson_value_get_object(payV); |
| 552 | check = json_timeline_setup_sql( "t", &sql, pay ); |
| 553 | if(check){ |
| 554 | g.json.resultCode = check; |
| 555 | goto error; |
| 556 | } |
| 557 | |
| 558 | db_multi_exec(blob_buffer(&sql)); |
| 559 | #define SET(K) if(0!=(check=cson_object_set(pay,K,tmp))){ \ |
| 560 | g.json.resultCode = (cson_rc.AllocError==check) \ |
| 561 | ? FSL_JSON_E_ALLOC : FSL_JSON_E_UNKNOWN; \ |
| 562 | goto error;\ |
| @@ -514,9 +621,10 @@ | |
| 621 | error: |
| 622 | assert( 0 != g.json.resultCode ); |
| 623 | cson_value_free(payV); |
| 624 | payV = NULL; |
| 625 | ok: |
| 626 | blob_reset(&sql); |
| 627 | db_finalize(&q); |
| 628 | return payV; |
| 629 | } |
| 630 | |
| 631 |