Fossil SCM
Improved origin tracking for unversioned files. Show Admin/Setup users the rcvid and allow them to see the originating IP address and to delete unversioned files. Update the rcvfrom table viewer to include unversioned file entries.
Commit
8972aae720b007e1d6a95c72de83b028fa39aa6c
Parent
7deeb51511eca13…
2 files changed
+79
-3
+13
-1
+79
-3
| --- src/shun.c | ||
| +++ src/shun.c | ||
| @@ -327,10 +327,15 @@ | ||
| 327 | 327 | } |
| 328 | 328 | db_multi_exec( |
| 329 | 329 | "CREATE TEMP TABLE rcvidUsed(x INTEGER PRIMARY KEY);" |
| 330 | 330 | "INSERT OR IGNORE INTO rcvidUsed(x) SELECT rcvid FROM blob;" |
| 331 | 331 | ); |
| 332 | + if( db_table_exists("repository","unversioned") ){ | |
| 333 | + db_multi_exec( | |
| 334 | + "INSERT OR IGNORE INTO rcvidUsed(x) SELECT rcvid FROM unversioned;" | |
| 335 | + ); | |
| 336 | + } | |
| 332 | 337 | db_prepare(&q, |
| 333 | 338 | "SELECT rcvid, login, datetime(rcvfrom.mtime), rcvfrom.ipaddr," |
| 334 | 339 | " EXISTS(SELECT 1 FROM rcvidUsed WHERE x=rcvfrom.rcvid)" |
| 335 | 340 | " FROM rcvfrom LEFT JOIN user USING(uid)" |
| 336 | 341 | " ORDER BY rcvid DESC LIMIT %d OFFSET %d", |
| @@ -389,10 +394,11 @@ | ||
| 389 | 394 | ** parameters. Requires Admin privilege. |
| 390 | 395 | */ |
| 391 | 396 | void rcvfrom_page(void){ |
| 392 | 397 | int rcvid = atoi(PD("rcvid","0")); |
| 393 | 398 | Stmt q; |
| 399 | + int cnt; | |
| 394 | 400 | |
| 395 | 401 | login_check_credentials(); |
| 396 | 402 | if( !g.perm.Admin ){ |
| 397 | 403 | login_needed(0); |
| 398 | 404 | return; |
| @@ -441,20 +447,90 @@ | ||
| 441 | 447 | db_prepare(&q, |
| 442 | 448 | "SELECT blob.rid, blob.uuid, blob.size, description.summary\n" |
| 443 | 449 | " FROM blob LEFT JOIN description ON (blob.rid=description.rid)" |
| 444 | 450 | " WHERE blob.rcvid=%d", rcvid |
| 445 | 451 | ); |
| 446 | - @ <tr><th valign="top" align="right">Artifacts:</th> | |
| 447 | - @ <td valign="top"> | |
| 452 | + cnt = 0; | |
| 448 | 453 | while( db_step(&q)==SQLITE_ROW ){ |
| 449 | 454 | const char *zUuid = db_column_text(&q, 1); |
| 450 | 455 | int size = db_column_int(&q, 2); |
| 451 | 456 | const char *zDesc = db_column_text(&q, 3); |
| 452 | 457 | if( zDesc==0 ) zDesc = ""; |
| 458 | + if( cnt==0 ){ | |
| 459 | + @ <tr><th valign="top" align="right">Artifacts:</th> | |
| 460 | + @ <td valign="top"> | |
| 461 | + } | |
| 462 | + cnt++; | |
| 453 | 463 | @ <a href="%R/info/%s(zUuid)">%s(zUuid)</a> |
| 454 | 464 | @ %h(zDesc) (size: %d(size))<br /> |
| 455 | 465 | } |
| 456 | - @ </td></tr> | |
| 466 | + if( cnt>0 ){ | |
| 467 | + @ <p> | |
| 468 | + if( db_exists( | |
| 469 | + "SELECT 1 FROM blob WHERE rcvid=%d AND" | |
| 470 | + " NOT EXISTS (SELECT 1 FROM shun WHERE shun.uuid=blob.uuid)", rcvid) | |
| 471 | + ){ | |
| 472 | + @ <form action='%R/shun'> | |
| 473 | + @ <input type="hidden" name="shun"> | |
| 474 | + @ <input type="hidden" name="rcvid" value='%d(rcvid)'> | |
| 475 | + @ <input type="submit" value="Shun All These Artifacts"> | |
| 476 | + @ </form> | |
| 477 | + } | |
| 478 | + if( db_exists( | |
| 479 | + "SELECT 1 FROM blob WHERE rcvid=%d AND" | |
| 480 | + " EXISTS (SELECT 1 FROM shun WHERE shun.uuid=blob.uuid)", rcvid) | |
| 481 | + ){ | |
| 482 | + @ <form action='%R/shun'> | |
| 483 | + @ <input type="hidden" name="unshun"> | |
| 484 | + @ <input type="hidden" name="rcvid" value='%d(rcvid)'> | |
| 485 | + @ <input type="submit" value="Unshun All These Artifacts"> | |
| 486 | + @ </form> | |
| 487 | + } | |
| 488 | + @ </td></tr> | |
| 489 | + } | |
| 490 | + if( db_table_exists("repository","unversioned") ){ | |
| 491 | + cnt = 0; | |
| 492 | + if( PB("uvdelete") && PB("confirmdelete") ){ | |
| 493 | + db_multi_exec( | |
| 494 | + "DELETE FROM unversioned WHERE rcvid=%d", rcvid | |
| 495 | + ); | |
| 496 | + } | |
| 497 | + db_finalize(&q); | |
| 498 | + db_prepare(&q, | |
| 499 | + "SELECT name, hash, sz\n" | |
| 500 | + " FROM unversioned " | |
| 501 | + " WHERE rcvid=%d", rcvid | |
| 502 | + | |
| 457 | 503 | ); |
| 504 | + while( db_step(&q)==SQLITE_ROW ){ | |
| 505 | + const char *zName = db_column_text(&q,0); | |
| 506 | + const char *zHash = db_column_text(&q,1); | |
| 507 | + int size = db_column_int(&q,2); | |
| 508 | + int isDeleted = zHash==0; | |
| 509 | + if( cnt==0 ){ | |
| 510 | + @ <tr><th valign="top" align="right">Unversioned Files:</th> | |
| 511 | + @ <td valign="top"> | |
| 512 | + } | |
| 513 | + cnt++; | |
| 514 | + if( isDeleted ){ | |
| 515 | + @ %h(zName) (deleted)<br /> | |
| 516 | + }else{ | |
| 517 | + @ <a href="%R/uv/%h(zName)">%h(zName)</a> (size: %d(size))<br /> | |
| 518 | + } | |
| 519 | + } | |
| 520 | + if( cnt>0 ){ | |
| 521 | + @ <p><form action='%R/rcvfrom'> | |
| 522 | + @ <input type="hidden" name="rcvid" value='%d(rcvid)'> | |
| 523 | + @ <input type="hidden" name="uvdelete" value="1"> | |
| 524 | + if( PB("uvdelete") ){ | |
| 525 | + @ <input type="hidden" name="confirmdelete" value="1"> | |
| 526 | + @ <input type="submit" value="Confirm Deletion of These Files"> | |
| 527 | + }else{ | |
| 528 | + @ <input type="submit" value="Delete These Unversioned Files"> | |
| 529 | + } | |
| 530 | + @ </form> | |
| 531 | + @ </td></tr> | |
| 532 | + } | |
| 533 | + } | |
| 458 | 534 | @ </table> |
| 459 | 535 | db_finalize(&q); |
| 460 | 536 | style_footer(); |
| 461 | 537 | } |
| 462 | 538 |
| --- src/shun.c | |
| +++ src/shun.c | |
| @@ -327,10 +327,15 @@ | |
| 327 | } |
| 328 | db_multi_exec( |
| 329 | "CREATE TEMP TABLE rcvidUsed(x INTEGER PRIMARY KEY);" |
| 330 | "INSERT OR IGNORE INTO rcvidUsed(x) SELECT rcvid FROM blob;" |
| 331 | ); |
| 332 | db_prepare(&q, |
| 333 | "SELECT rcvid, login, datetime(rcvfrom.mtime), rcvfrom.ipaddr," |
| 334 | " EXISTS(SELECT 1 FROM rcvidUsed WHERE x=rcvfrom.rcvid)" |
| 335 | " FROM rcvfrom LEFT JOIN user USING(uid)" |
| 336 | " ORDER BY rcvid DESC LIMIT %d OFFSET %d", |
| @@ -389,10 +394,11 @@ | |
| 389 | ** parameters. Requires Admin privilege. |
| 390 | */ |
| 391 | void rcvfrom_page(void){ |
| 392 | int rcvid = atoi(PD("rcvid","0")); |
| 393 | Stmt q; |
| 394 | |
| 395 | login_check_credentials(); |
| 396 | if( !g.perm.Admin ){ |
| 397 | login_needed(0); |
| 398 | return; |
| @@ -441,20 +447,90 @@ | |
| 441 | db_prepare(&q, |
| 442 | "SELECT blob.rid, blob.uuid, blob.size, description.summary\n" |
| 443 | " FROM blob LEFT JOIN description ON (blob.rid=description.rid)" |
| 444 | " WHERE blob.rcvid=%d", rcvid |
| 445 | ); |
| 446 | @ <tr><th valign="top" align="right">Artifacts:</th> |
| 447 | @ <td valign="top"> |
| 448 | while( db_step(&q)==SQLITE_ROW ){ |
| 449 | const char *zUuid = db_column_text(&q, 1); |
| 450 | int size = db_column_int(&q, 2); |
| 451 | const char *zDesc = db_column_text(&q, 3); |
| 452 | if( zDesc==0 ) zDesc = ""; |
| 453 | @ <a href="%R/info/%s(zUuid)">%s(zUuid)</a> |
| 454 | @ %h(zDesc) (size: %d(size))<br /> |
| 455 | } |
| 456 | @ </td></tr> |
| 457 | ); |
| 458 | @ </table> |
| 459 | db_finalize(&q); |
| 460 | style_footer(); |
| 461 | } |
| 462 |
| --- src/shun.c | |
| +++ src/shun.c | |
| @@ -327,10 +327,15 @@ | |
| 327 | } |
| 328 | db_multi_exec( |
| 329 | "CREATE TEMP TABLE rcvidUsed(x INTEGER PRIMARY KEY);" |
| 330 | "INSERT OR IGNORE INTO rcvidUsed(x) SELECT rcvid FROM blob;" |
| 331 | ); |
| 332 | if( db_table_exists("repository","unversioned") ){ |
| 333 | db_multi_exec( |
| 334 | "INSERT OR IGNORE INTO rcvidUsed(x) SELECT rcvid FROM unversioned;" |
| 335 | ); |
| 336 | } |
| 337 | db_prepare(&q, |
| 338 | "SELECT rcvid, login, datetime(rcvfrom.mtime), rcvfrom.ipaddr," |
| 339 | " EXISTS(SELECT 1 FROM rcvidUsed WHERE x=rcvfrom.rcvid)" |
| 340 | " FROM rcvfrom LEFT JOIN user USING(uid)" |
| 341 | " ORDER BY rcvid DESC LIMIT %d OFFSET %d", |
| @@ -389,10 +394,11 @@ | |
| 394 | ** parameters. Requires Admin privilege. |
| 395 | */ |
| 396 | void rcvfrom_page(void){ |
| 397 | int rcvid = atoi(PD("rcvid","0")); |
| 398 | Stmt q; |
| 399 | int cnt; |
| 400 | |
| 401 | login_check_credentials(); |
| 402 | if( !g.perm.Admin ){ |
| 403 | login_needed(0); |
| 404 | return; |
| @@ -441,20 +447,90 @@ | |
| 447 | db_prepare(&q, |
| 448 | "SELECT blob.rid, blob.uuid, blob.size, description.summary\n" |
| 449 | " FROM blob LEFT JOIN description ON (blob.rid=description.rid)" |
| 450 | " WHERE blob.rcvid=%d", rcvid |
| 451 | ); |
| 452 | cnt = 0; |
| 453 | while( db_step(&q)==SQLITE_ROW ){ |
| 454 | const char *zUuid = db_column_text(&q, 1); |
| 455 | int size = db_column_int(&q, 2); |
| 456 | const char *zDesc = db_column_text(&q, 3); |
| 457 | if( zDesc==0 ) zDesc = ""; |
| 458 | if( cnt==0 ){ |
| 459 | @ <tr><th valign="top" align="right">Artifacts:</th> |
| 460 | @ <td valign="top"> |
| 461 | } |
| 462 | cnt++; |
| 463 | @ <a href="%R/info/%s(zUuid)">%s(zUuid)</a> |
| 464 | @ %h(zDesc) (size: %d(size))<br /> |
| 465 | } |
| 466 | if( cnt>0 ){ |
| 467 | @ <p> |
| 468 | if( db_exists( |
| 469 | "SELECT 1 FROM blob WHERE rcvid=%d AND" |
| 470 | " NOT EXISTS (SELECT 1 FROM shun WHERE shun.uuid=blob.uuid)", rcvid) |
| 471 | ){ |
| 472 | @ <form action='%R/shun'> |
| 473 | @ <input type="hidden" name="shun"> |
| 474 | @ <input type="hidden" name="rcvid" value='%d(rcvid)'> |
| 475 | @ <input type="submit" value="Shun All These Artifacts"> |
| 476 | @ </form> |
| 477 | } |
| 478 | if( db_exists( |
| 479 | "SELECT 1 FROM blob WHERE rcvid=%d AND" |
| 480 | " EXISTS (SELECT 1 FROM shun WHERE shun.uuid=blob.uuid)", rcvid) |
| 481 | ){ |
| 482 | @ <form action='%R/shun'> |
| 483 | @ <input type="hidden" name="unshun"> |
| 484 | @ <input type="hidden" name="rcvid" value='%d(rcvid)'> |
| 485 | @ <input type="submit" value="Unshun All These Artifacts"> |
| 486 | @ </form> |
| 487 | } |
| 488 | @ </td></tr> |
| 489 | } |
| 490 | if( db_table_exists("repository","unversioned") ){ |
| 491 | cnt = 0; |
| 492 | if( PB("uvdelete") && PB("confirmdelete") ){ |
| 493 | db_multi_exec( |
| 494 | "DELETE FROM unversioned WHERE rcvid=%d", rcvid |
| 495 | ); |
| 496 | } |
| 497 | db_finalize(&q); |
| 498 | db_prepare(&q, |
| 499 | "SELECT name, hash, sz\n" |
| 500 | " FROM unversioned " |
| 501 | " WHERE rcvid=%d", rcvid |
| 502 | |
| 503 | ); |
| 504 | while( db_step(&q)==SQLITE_ROW ){ |
| 505 | const char *zName = db_column_text(&q,0); |
| 506 | const char *zHash = db_column_text(&q,1); |
| 507 | int size = db_column_int(&q,2); |
| 508 | int isDeleted = zHash==0; |
| 509 | if( cnt==0 ){ |
| 510 | @ <tr><th valign="top" align="right">Unversioned Files:</th> |
| 511 | @ <td valign="top"> |
| 512 | } |
| 513 | cnt++; |
| 514 | if( isDeleted ){ |
| 515 | @ %h(zName) (deleted)<br /> |
| 516 | }else{ |
| 517 | @ <a href="%R/uv/%h(zName)">%h(zName)</a> (size: %d(size))<br /> |
| 518 | } |
| 519 | } |
| 520 | if( cnt>0 ){ |
| 521 | @ <p><form action='%R/rcvfrom'> |
| 522 | @ <input type="hidden" name="rcvid" value='%d(rcvid)'> |
| 523 | @ <input type="hidden" name="uvdelete" value="1"> |
| 524 | if( PB("uvdelete") ){ |
| 525 | @ <input type="hidden" name="confirmdelete" value="1"> |
| 526 | @ <input type="submit" value="Confirm Deletion of These Files"> |
| 527 | }else{ |
| 528 | @ <input type="submit" value="Delete These Unversioned Files"> |
| 529 | } |
| 530 | @ </form> |
| 531 | @ </td></tr> |
| 532 | } |
| 533 | } |
| 534 | @ </table> |
| 535 | db_finalize(&q); |
| 536 | style_footer(); |
| 537 | } |
| 538 |
+13
-1
| --- src/unversioned.c | ||
| +++ src/unversioned.c | ||
| @@ -376,11 +376,12 @@ | ||
| 376 | 376 | " name," |
| 377 | 377 | " mtime," |
| 378 | 378 | " hash," |
| 379 | 379 | " sz," |
| 380 | 380 | " (SELECT login FROM rcvfrom, user" |
| 381 | - " WHERE user.uid=rcvfrom.uid AND rcvfrom.rcvid=unversioned.rcvid)" | |
| 381 | + " WHERE user.uid=rcvfrom.uid AND rcvfrom.rcvid=unversioned.rcvid)," | |
| 382 | + " rcvid" | |
| 382 | 383 | " FROM unversioned" |
| 383 | 384 | ); |
| 384 | 385 | iNow = db_int64(0, "SELECT strftime('%%s','now');"); |
| 385 | 386 | while( db_step(&q)==SQLITE_ROW ){ |
| 386 | 387 | const char *zName = db_column_text(&q, 0); |
| @@ -388,10 +389,11 @@ | ||
| 388 | 389 | const char *zHash = db_column_text(&q, 2); |
| 389 | 390 | int isDeleted = zHash==0; |
| 390 | 391 | int fullSize = db_column_int(&q, 3); |
| 391 | 392 | char *zAge = human_readable_age((iNow - mtime)/86400.0); |
| 392 | 393 | const char *zLogin = db_column_text(&q, 4); |
| 394 | + int rcvid = db_column_int(&q,5); | |
| 393 | 395 | if( zLogin==0 ) zLogin = ""; |
| 394 | 396 | if( (n++)==0 ){ |
| 395 | 397 | @ <div class="uvlist"> |
| 396 | 398 | @ <table cellpadding="2" cellspacing="0" border="1" id="uvtab"> |
| 397 | 399 | @ <thead><tr> |
| @@ -398,10 +400,13 @@ | ||
| 398 | 400 | @ <th> Name |
| 399 | 401 | @ <th> Age |
| 400 | 402 | @ <th> Size |
| 401 | 403 | @ <th> User |
| 402 | 404 | @ <th> SHA1 |
| 405 | + if( g.perm.Admin ){ | |
| 406 | + @ <th> rcvid | |
| 407 | + } | |
| 403 | 408 | @ </tr></thead> |
| 404 | 409 | @ <tbody> |
| 405 | 410 | } |
| 406 | 411 | if( isDeleted ){ |
| 407 | 412 | sqlite3_snprintf(sizeof(zSzName), zSzName, "<i>Deleted</i>"); |
| @@ -416,10 +421,17 @@ | ||
| 416 | 421 | @ <td> <a href='%R/uv/%T(zName)'>%h(zName)</a> </td> |
| 417 | 422 | @ <td data-sortkey='%016llx(-mtime)'> %s(zAge) </td> |
| 418 | 423 | @ <td data-sortkey='%08x(fullSize)'> %s(zSzName) </td> |
| 419 | 424 | @ <td> %h(zLogin) </td> |
| 420 | 425 | @ <td> %h(zHash) </td> |
| 426 | + if( g.perm.Admin ){ | |
| 427 | + if( rcvid ){ | |
| 428 | + @ <td> <a href="%R/rcvfrom?rcvid=%d(rcvid)">%d(rcvid)</a> | |
| 429 | + }else{ | |
| 430 | + @ <td> | |
| 431 | + } | |
| 432 | + } | |
| 421 | 433 | @ </tr> |
| 422 | 434 | fossil_free(zAge); |
| 423 | 435 | } |
| 424 | 436 | db_finalize(&q); |
| 425 | 437 | if( n ){ |
| 426 | 438 |
| --- src/unversioned.c | |
| +++ src/unversioned.c | |
| @@ -376,11 +376,12 @@ | |
| 376 | " name," |
| 377 | " mtime," |
| 378 | " hash," |
| 379 | " sz," |
| 380 | " (SELECT login FROM rcvfrom, user" |
| 381 | " WHERE user.uid=rcvfrom.uid AND rcvfrom.rcvid=unversioned.rcvid)" |
| 382 | " FROM unversioned" |
| 383 | ); |
| 384 | iNow = db_int64(0, "SELECT strftime('%%s','now');"); |
| 385 | while( db_step(&q)==SQLITE_ROW ){ |
| 386 | const char *zName = db_column_text(&q, 0); |
| @@ -388,10 +389,11 @@ | |
| 388 | const char *zHash = db_column_text(&q, 2); |
| 389 | int isDeleted = zHash==0; |
| 390 | int fullSize = db_column_int(&q, 3); |
| 391 | char *zAge = human_readable_age((iNow - mtime)/86400.0); |
| 392 | const char *zLogin = db_column_text(&q, 4); |
| 393 | if( zLogin==0 ) zLogin = ""; |
| 394 | if( (n++)==0 ){ |
| 395 | @ <div class="uvlist"> |
| 396 | @ <table cellpadding="2" cellspacing="0" border="1" id="uvtab"> |
| 397 | @ <thead><tr> |
| @@ -398,10 +400,13 @@ | |
| 398 | @ <th> Name |
| 399 | @ <th> Age |
| 400 | @ <th> Size |
| 401 | @ <th> User |
| 402 | @ <th> SHA1 |
| 403 | @ </tr></thead> |
| 404 | @ <tbody> |
| 405 | } |
| 406 | if( isDeleted ){ |
| 407 | sqlite3_snprintf(sizeof(zSzName), zSzName, "<i>Deleted</i>"); |
| @@ -416,10 +421,17 @@ | |
| 416 | @ <td> <a href='%R/uv/%T(zName)'>%h(zName)</a> </td> |
| 417 | @ <td data-sortkey='%016llx(-mtime)'> %s(zAge) </td> |
| 418 | @ <td data-sortkey='%08x(fullSize)'> %s(zSzName) </td> |
| 419 | @ <td> %h(zLogin) </td> |
| 420 | @ <td> %h(zHash) </td> |
| 421 | @ </tr> |
| 422 | fossil_free(zAge); |
| 423 | } |
| 424 | db_finalize(&q); |
| 425 | if( n ){ |
| 426 |
| --- src/unversioned.c | |
| +++ src/unversioned.c | |
| @@ -376,11 +376,12 @@ | |
| 376 | " name," |
| 377 | " mtime," |
| 378 | " hash," |
| 379 | " sz," |
| 380 | " (SELECT login FROM rcvfrom, user" |
| 381 | " WHERE user.uid=rcvfrom.uid AND rcvfrom.rcvid=unversioned.rcvid)," |
| 382 | " rcvid" |
| 383 | " FROM unversioned" |
| 384 | ); |
| 385 | iNow = db_int64(0, "SELECT strftime('%%s','now');"); |
| 386 | while( db_step(&q)==SQLITE_ROW ){ |
| 387 | const char *zName = db_column_text(&q, 0); |
| @@ -388,10 +389,11 @@ | |
| 389 | const char *zHash = db_column_text(&q, 2); |
| 390 | int isDeleted = zHash==0; |
| 391 | int fullSize = db_column_int(&q, 3); |
| 392 | char *zAge = human_readable_age((iNow - mtime)/86400.0); |
| 393 | const char *zLogin = db_column_text(&q, 4); |
| 394 | int rcvid = db_column_int(&q,5); |
| 395 | if( zLogin==0 ) zLogin = ""; |
| 396 | if( (n++)==0 ){ |
| 397 | @ <div class="uvlist"> |
| 398 | @ <table cellpadding="2" cellspacing="0" border="1" id="uvtab"> |
| 399 | @ <thead><tr> |
| @@ -398,10 +400,13 @@ | |
| 400 | @ <th> Name |
| 401 | @ <th> Age |
| 402 | @ <th> Size |
| 403 | @ <th> User |
| 404 | @ <th> SHA1 |
| 405 | if( g.perm.Admin ){ |
| 406 | @ <th> rcvid |
| 407 | } |
| 408 | @ </tr></thead> |
| 409 | @ <tbody> |
| 410 | } |
| 411 | if( isDeleted ){ |
| 412 | sqlite3_snprintf(sizeof(zSzName), zSzName, "<i>Deleted</i>"); |
| @@ -416,10 +421,17 @@ | |
| 421 | @ <td> <a href='%R/uv/%T(zName)'>%h(zName)</a> </td> |
| 422 | @ <td data-sortkey='%016llx(-mtime)'> %s(zAge) </td> |
| 423 | @ <td data-sortkey='%08x(fullSize)'> %s(zSzName) </td> |
| 424 | @ <td> %h(zLogin) </td> |
| 425 | @ <td> %h(zHash) </td> |
| 426 | if( g.perm.Admin ){ |
| 427 | if( rcvid ){ |
| 428 | @ <td> <a href="%R/rcvfrom?rcvid=%d(rcvid)">%d(rcvid)</a> |
| 429 | }else{ |
| 430 | @ <td> |
| 431 | } |
| 432 | } |
| 433 | @ </tr> |
| 434 | fossil_free(zAge); |
| 435 | } |
| 436 | db_finalize(&q); |
| 437 | if( n ){ |
| 438 |