Fossil SCM

Correct the new attachment-target-to-rid code to return the tip version RID of forum posts. General cleanups in attachment-related code. Add some test code for resolving attachment targets.

stephan 2026-06-04 15:15 UTC attach-v2
Commit 830cad5676d99e3051e93c8f0da01bc1a9579b0a58d2b5600f16818d1dd5c415
1 file changed +66 -25
+66 -25
--- src/attach.c
+++ src/attach.c
@@ -130,10 +130,13 @@
130130
"AND b.uuid %s '%q%s'",
131131
bFull ? "=" : "GLOB"/*safe-for-%s*/,
132132
zTarget,
133133
bFull ? "" : "*"/*safe-for-%s*/
134134
);
135
+ if( rid>0 ){
136
+ rid = forumpost_head_rid(rid);
137
+ }
135138
break;
136139
case CFTYPE_WIKI:
137140
rid = db_int(
138141
0, "SELECT b.rid FROM blob b, tag t, tagxref x\n"
139142
"WHERE tagname='wiki-%q'\n"
@@ -1310,15 +1313,18 @@
13101313
char szBuf[36] = {0}; /* scratchpad for attachment size value */
13111314
const char *zLinkTgt = (ATTACHLIST_TARGET_BLANK & flags)
13121315
? " target=\"_blank\"" : "";
13131316
Stmt q;
13141317
db_prepare(&q,
1315
- "SELECT datetime(mtime,toLocal()), filename, user,"
1316
- " (SELECT uuid FROM blob WHERE rid=attachid), src, target, "
1317
- " attachid "
1318
- " FROM attachment"
1319
- " WHERE isLatest AND src!='' AND target=%Q"
1318
+ "SELECT datetime(mtime,toLocal()), a.filename, a.user,"
1319
+ " b1.uuid, a.src, a.target, a.attachid, b2.size\n"
1320
+ " FROM attachment a, blob b1, blob b2\n"
1321
+ " WHERE a.isLatest\n"
1322
+ " AND a.src IS NOT NULL\n"
1323
+ " AND a.target=%Q\n"
1324
+ " AND b1.rid=a.attachid\n"
1325
+ " AND b2.uuid=a.src\n"
13201326
" ORDER BY mtime DESC",
13211327
zTarget
13221328
);
13231329
while( db_step(&q)==SQLITE_ROW ){
13241330
const char *zDate = db_column_text(&q, 0);
@@ -1328,11 +1334,11 @@
13281334
const char *zSrc = db_column_text(&q, 4);
13291335
const char *zTarget = db_column_text(&q, 5);
13301336
const char *zDispUser = zUser && zUser[0] ? zUser : "anonymous";
13311337
const char *zTypeArg = 0; /* URL arg name for /attachdownload */
13321338
const int aid = db_column_int(&q, 6);
1333
- const int iAType = attachment_target_type(zTarget, 1);
1339
+ const int sz = db_column_int(&q, 7);
13341340
if( (flags & ATTACHLIST_HIDE_UNAPPROVED)
13351341
&& moderation_pending(aid)
13361342
&& !moderation_user_could(aid, 1, 0) ){
13371343
continue;
13381344
}
@@ -1343,21 +1349,20 @@
13431349
}
13441350
@ %s(zHeader)
13451351
@ <ul>
13461352
}
13471353
cnt++;
1348
- switch( iAType ){
1354
+ switch( attachment_target_type(zTarget, 1) ){
13491355
case CFTYPE_TICKET: zTypeArg = "tkt"; break;
13501356
case CFTYPE_FORUM: zTypeArg = "forumpost"; break;
13511357
case CFTYPE_EVENT: zTypeArg = "technote"; break;
13521358
case CFTYPE_WIKI:
13531359
default: zTypeArg = "page"; break;
13541360
}
13551361
@ <li>
13561362
@ <a href="%R/artifact/%!S(zSrc)"%s(zLinkTgt)>%h(zFile)</a>
13571363
if( flags & ATTACHLIST_SIZE ){
1358
- const int sz = db_int(0,"SELECT size FROM blob WHERE uuid=%Q", zSrc);
13591364
sqlite3_snprintf(sizeof(szBuf), szBuf, " %d bytes", sz);
13601365
}
13611366
@ [<a href="%R/attachdownload/%t(zFile)?%s(zTypeArg)=%t(zTarget)\
13621367
@&file=%t(zFile)%s(zLinkTgt)">download</a>%s(szBuf)]
13631368
@ added by %h(zDispUser) on
@@ -1583,13 +1588,13 @@
15831588
db_prepare(&q,
15841589
"SELECT datetime(mtime), a.src, a.target, a.filename, a.isLatest,\n"
15851590
" b2.size, b1.uuid, a.user, a.comment\n"
15861591
" FROM attachment a, blob b1, blob b2\n"
15871592
" WHERE a.target=%Q\n"
1593
+ " AND a.src IS NOT NULL\n"
15881594
" AND b1.rid=a.attachid\n"
15891595
" AND b2.uuid=a.src\n"
1590
- " AND b2.size>0\n"
15911596
" AND (a.isLatest OR %d)\n"
15921597
" ORDER BY a.target, a.isLatest DESC, a.mtime DESC\n",
15931598
zTgt, !bLatestOnly
15941599
);
15951600
while(SQLITE_ROW == db_step(&q)){
@@ -1635,10 +1640,41 @@
16351640
}else{
16361641
blob_append_char(pOut, ']');
16371642
}
16381643
return i;
16391644
}
1645
+
1646
+/*
1647
+** COMMAND: test-attachment-target
1648
+**
1649
+** Usage: %fossil test-attachment-target TARGET_ID...
1650
+*/
1651
+void test_attachment_target_type_cmd(void){
1652
+ int i;
1653
+ verify_all_options();
1654
+ db_find_and_open_repository(0, 0);
1655
+ if( g.argc<3 ){
1656
+ usage("test-attachment-target TARGET_ID");
1657
+ return;
1658
+ }
1659
+ for( i = 2; i < g.argc; ++i ){
1660
+ const char *zTarget = g.argv[i];
1661
+ const int rid = attachment_target_rid(zTarget, 0);
1662
+ const int type = attachment_target_type(zTarget, 0);
1663
+ const char *zType = "<invalid>";
1664
+ switch(type){
1665
+ case CFTYPE_EVENT: zType = "e"; break;
1666
+ case CFTYPE_FORUM: zType = "f"; break;
1667
+ case CFTYPE_TICKET: zType = "t"; break;
1668
+ case CFTYPE_WIKI: zType = "w"; break;
1669
+ }
1670
+ fossil_print("%-20s = %s %d %z\n",
1671
+ zTarget, zType, rid,
1672
+ rid>0 ? rid_to_uuid(rid) : 0);
1673
+ }
1674
+}
1675
+
16401676
16411677
/*
16421678
** COMMAND: test-attachments-to-json
16431679
**
16441680
** Usage: %fossil test-attachments-to-json TARGET_ID
@@ -1657,29 +1693,34 @@
16571693
void test_attachments_to_json_cmd(void){
16581694
Manifest *pManifest;
16591695
Blob b = BLOB_INITIALIZER;
16601696
int bLatestOnly = find_option("old",0,0)==0;
16611697
int emptyPolicy = 1;
1662
- int rid;
1698
+ int rid, i;
16631699
int bFullId = find_option("full",0,0)!=0;
1664
- const char *zTarget;
16651700
verify_all_options();
16661701
db_find_and_open_repository(0, 0);
16671702
if( g.argc<3 ){
16681703
usage("test-attachments-to-json TARGET_ID");
16691704
return;
16701705
}
1671
- zTarget = g.argv[2];
1672
- rid = attachment_target_rid(zTarget, bFullId);
1673
- if( 0==rid ){
1674
- fossil_fatal("Cannot resolve ID.");
1675
- }
1676
- pManifest = manifest_get(rid, CFTYPE_ANY, NULL);
1677
- attachments_to_json(pManifest, &b, bLatestOnly, emptyPolicy);
1678
- if( b.nUsed ){
1679
- char *zPretty = db_text(0,"SELECT json_pretty(%B)", &b);
1680
- fossil_print("%s\n", zPretty);
1681
- fossil_free(zPretty);
1682
- }
1683
- blob_reset(&b);
1684
- manifest_destroy(pManifest);
1706
+ for( i = 2; i < g.argc; ++i ){
1707
+ const char *zTarget = g.argv[i];
1708
+ rid = attachment_target_rid(zTarget, bFullId);
1709
+ if( 0==rid ){
1710
+ fossil_print("** cannot resolve %s\n", zTarget);
1711
+ continue;
1712
+ }
1713
+ pManifest = manifest_get(rid, CFTYPE_ANY, NULL);
1714
+ attachments_to_json(pManifest, &b, bLatestOnly, emptyPolicy);
1715
+ fossil_print("Attachments for %s: ", zTarget);
1716
+ if( b.nUsed ){
1717
+ char *zPretty = db_text(0,"SELECT json_pretty(%B)", &b);
1718
+ fossil_print("%s\n", zPretty);
1719
+ fossil_free(zPretty);
1720
+ }else{
1721
+ fossil_print("none\n");
1722
+ }
1723
+ blob_reset(&b);
1724
+ manifest_destroy(pManifest);
1725
+ }
16851726
}
16861727
--- src/attach.c
+++ src/attach.c
@@ -130,10 +130,13 @@
130 "AND b.uuid %s '%q%s'",
131 bFull ? "=" : "GLOB"/*safe-for-%s*/,
132 zTarget,
133 bFull ? "" : "*"/*safe-for-%s*/
134 );
 
 
 
135 break;
136 case CFTYPE_WIKI:
137 rid = db_int(
138 0, "SELECT b.rid FROM blob b, tag t, tagxref x\n"
139 "WHERE tagname='wiki-%q'\n"
@@ -1310,15 +1313,18 @@
1310 char szBuf[36] = {0}; /* scratchpad for attachment size value */
1311 const char *zLinkTgt = (ATTACHLIST_TARGET_BLANK & flags)
1312 ? " target=\"_blank\"" : "";
1313 Stmt q;
1314 db_prepare(&q,
1315 "SELECT datetime(mtime,toLocal()), filename, user,"
1316 " (SELECT uuid FROM blob WHERE rid=attachid), src, target, "
1317 " attachid "
1318 " FROM attachment"
1319 " WHERE isLatest AND src!='' AND target=%Q"
 
 
 
1320 " ORDER BY mtime DESC",
1321 zTarget
1322 );
1323 while( db_step(&q)==SQLITE_ROW ){
1324 const char *zDate = db_column_text(&q, 0);
@@ -1328,11 +1334,11 @@
1328 const char *zSrc = db_column_text(&q, 4);
1329 const char *zTarget = db_column_text(&q, 5);
1330 const char *zDispUser = zUser && zUser[0] ? zUser : "anonymous";
1331 const char *zTypeArg = 0; /* URL arg name for /attachdownload */
1332 const int aid = db_column_int(&q, 6);
1333 const int iAType = attachment_target_type(zTarget, 1);
1334 if( (flags & ATTACHLIST_HIDE_UNAPPROVED)
1335 && moderation_pending(aid)
1336 && !moderation_user_could(aid, 1, 0) ){
1337 continue;
1338 }
@@ -1343,21 +1349,20 @@
1343 }
1344 @ %s(zHeader)
1345 @ <ul>
1346 }
1347 cnt++;
1348 switch( iAType ){
1349 case CFTYPE_TICKET: zTypeArg = "tkt"; break;
1350 case CFTYPE_FORUM: zTypeArg = "forumpost"; break;
1351 case CFTYPE_EVENT: zTypeArg = "technote"; break;
1352 case CFTYPE_WIKI:
1353 default: zTypeArg = "page"; break;
1354 }
1355 @ <li>
1356 @ <a href="%R/artifact/%!S(zSrc)"%s(zLinkTgt)>%h(zFile)</a>
1357 if( flags & ATTACHLIST_SIZE ){
1358 const int sz = db_int(0,"SELECT size FROM blob WHERE uuid=%Q", zSrc);
1359 sqlite3_snprintf(sizeof(szBuf), szBuf, " %d bytes", sz);
1360 }
1361 @ [<a href="%R/attachdownload/%t(zFile)?%s(zTypeArg)=%t(zTarget)\
1362 @&file=%t(zFile)%s(zLinkTgt)">download</a>%s(szBuf)]
1363 @ added by %h(zDispUser) on
@@ -1583,13 +1588,13 @@
1583 db_prepare(&q,
1584 "SELECT datetime(mtime), a.src, a.target, a.filename, a.isLatest,\n"
1585 " b2.size, b1.uuid, a.user, a.comment\n"
1586 " FROM attachment a, blob b1, blob b2\n"
1587 " WHERE a.target=%Q\n"
 
1588 " AND b1.rid=a.attachid\n"
1589 " AND b2.uuid=a.src\n"
1590 " AND b2.size>0\n"
1591 " AND (a.isLatest OR %d)\n"
1592 " ORDER BY a.target, a.isLatest DESC, a.mtime DESC\n",
1593 zTgt, !bLatestOnly
1594 );
1595 while(SQLITE_ROW == db_step(&q)){
@@ -1635,10 +1640,41 @@
1635 }else{
1636 blob_append_char(pOut, ']');
1637 }
1638 return i;
1639 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1640
1641 /*
1642 ** COMMAND: test-attachments-to-json
1643 **
1644 ** Usage: %fossil test-attachments-to-json TARGET_ID
@@ -1657,29 +1693,34 @@
1657 void test_attachments_to_json_cmd(void){
1658 Manifest *pManifest;
1659 Blob b = BLOB_INITIALIZER;
1660 int bLatestOnly = find_option("old",0,0)==0;
1661 int emptyPolicy = 1;
1662 int rid;
1663 int bFullId = find_option("full",0,0)!=0;
1664 const char *zTarget;
1665 verify_all_options();
1666 db_find_and_open_repository(0, 0);
1667 if( g.argc<3 ){
1668 usage("test-attachments-to-json TARGET_ID");
1669 return;
1670 }
1671 zTarget = g.argv[2];
1672 rid = attachment_target_rid(zTarget, bFullId);
1673 if( 0==rid ){
1674 fossil_fatal("Cannot resolve ID.");
1675 }
1676 pManifest = manifest_get(rid, CFTYPE_ANY, NULL);
1677 attachments_to_json(pManifest, &b, bLatestOnly, emptyPolicy);
1678 if( b.nUsed ){
1679 char *zPretty = db_text(0,"SELECT json_pretty(%B)", &b);
1680 fossil_print("%s\n", zPretty);
1681 fossil_free(zPretty);
1682 }
1683 blob_reset(&b);
1684 manifest_destroy(pManifest);
 
 
 
 
 
 
1685 }
1686
--- src/attach.c
+++ src/attach.c
@@ -130,10 +130,13 @@
130 "AND b.uuid %s '%q%s'",
131 bFull ? "=" : "GLOB"/*safe-for-%s*/,
132 zTarget,
133 bFull ? "" : "*"/*safe-for-%s*/
134 );
135 if( rid>0 ){
136 rid = forumpost_head_rid(rid);
137 }
138 break;
139 case CFTYPE_WIKI:
140 rid = db_int(
141 0, "SELECT b.rid FROM blob b, tag t, tagxref x\n"
142 "WHERE tagname='wiki-%q'\n"
@@ -1310,15 +1313,18 @@
1313 char szBuf[36] = {0}; /* scratchpad for attachment size value */
1314 const char *zLinkTgt = (ATTACHLIST_TARGET_BLANK & flags)
1315 ? " target=\"_blank\"" : "";
1316 Stmt q;
1317 db_prepare(&q,
1318 "SELECT datetime(mtime,toLocal()), a.filename, a.user,"
1319 " b1.uuid, a.src, a.target, a.attachid, b2.size\n"
1320 " FROM attachment a, blob b1, blob b2\n"
1321 " WHERE a.isLatest\n"
1322 " AND a.src IS NOT NULL\n"
1323 " AND a.target=%Q\n"
1324 " AND b1.rid=a.attachid\n"
1325 " AND b2.uuid=a.src\n"
1326 " ORDER BY mtime DESC",
1327 zTarget
1328 );
1329 while( db_step(&q)==SQLITE_ROW ){
1330 const char *zDate = db_column_text(&q, 0);
@@ -1328,11 +1334,11 @@
1334 const char *zSrc = db_column_text(&q, 4);
1335 const char *zTarget = db_column_text(&q, 5);
1336 const char *zDispUser = zUser && zUser[0] ? zUser : "anonymous";
1337 const char *zTypeArg = 0; /* URL arg name for /attachdownload */
1338 const int aid = db_column_int(&q, 6);
1339 const int sz = db_column_int(&q, 7);
1340 if( (flags & ATTACHLIST_HIDE_UNAPPROVED)
1341 && moderation_pending(aid)
1342 && !moderation_user_could(aid, 1, 0) ){
1343 continue;
1344 }
@@ -1343,21 +1349,20 @@
1349 }
1350 @ %s(zHeader)
1351 @ <ul>
1352 }
1353 cnt++;
1354 switch( attachment_target_type(zTarget, 1) ){
1355 case CFTYPE_TICKET: zTypeArg = "tkt"; break;
1356 case CFTYPE_FORUM: zTypeArg = "forumpost"; break;
1357 case CFTYPE_EVENT: zTypeArg = "technote"; break;
1358 case CFTYPE_WIKI:
1359 default: zTypeArg = "page"; break;
1360 }
1361 @ <li>
1362 @ <a href="%R/artifact/%!S(zSrc)"%s(zLinkTgt)>%h(zFile)</a>
1363 if( flags & ATTACHLIST_SIZE ){
 
1364 sqlite3_snprintf(sizeof(szBuf), szBuf, " %d bytes", sz);
1365 }
1366 @ [<a href="%R/attachdownload/%t(zFile)?%s(zTypeArg)=%t(zTarget)\
1367 @&file=%t(zFile)%s(zLinkTgt)">download</a>%s(szBuf)]
1368 @ added by %h(zDispUser) on
@@ -1583,13 +1588,13 @@
1588 db_prepare(&q,
1589 "SELECT datetime(mtime), a.src, a.target, a.filename, a.isLatest,\n"
1590 " b2.size, b1.uuid, a.user, a.comment\n"
1591 " FROM attachment a, blob b1, blob b2\n"
1592 " WHERE a.target=%Q\n"
1593 " AND a.src IS NOT NULL\n"
1594 " AND b1.rid=a.attachid\n"
1595 " AND b2.uuid=a.src\n"
 
1596 " AND (a.isLatest OR %d)\n"
1597 " ORDER BY a.target, a.isLatest DESC, a.mtime DESC\n",
1598 zTgt, !bLatestOnly
1599 );
1600 while(SQLITE_ROW == db_step(&q)){
@@ -1635,10 +1640,41 @@
1640 }else{
1641 blob_append_char(pOut, ']');
1642 }
1643 return i;
1644 }
1645
1646 /*
1647 ** COMMAND: test-attachment-target
1648 **
1649 ** Usage: %fossil test-attachment-target TARGET_ID...
1650 */
1651 void test_attachment_target_type_cmd(void){
1652 int i;
1653 verify_all_options();
1654 db_find_and_open_repository(0, 0);
1655 if( g.argc<3 ){
1656 usage("test-attachment-target TARGET_ID");
1657 return;
1658 }
1659 for( i = 2; i < g.argc; ++i ){
1660 const char *zTarget = g.argv[i];
1661 const int rid = attachment_target_rid(zTarget, 0);
1662 const int type = attachment_target_type(zTarget, 0);
1663 const char *zType = "<invalid>";
1664 switch(type){
1665 case CFTYPE_EVENT: zType = "e"; break;
1666 case CFTYPE_FORUM: zType = "f"; break;
1667 case CFTYPE_TICKET: zType = "t"; break;
1668 case CFTYPE_WIKI: zType = "w"; break;
1669 }
1670 fossil_print("%-20s = %s %d %z\n",
1671 zTarget, zType, rid,
1672 rid>0 ? rid_to_uuid(rid) : 0);
1673 }
1674 }
1675
1676
1677 /*
1678 ** COMMAND: test-attachments-to-json
1679 **
1680 ** Usage: %fossil test-attachments-to-json TARGET_ID
@@ -1657,29 +1693,34 @@
1693 void test_attachments_to_json_cmd(void){
1694 Manifest *pManifest;
1695 Blob b = BLOB_INITIALIZER;
1696 int bLatestOnly = find_option("old",0,0)==0;
1697 int emptyPolicy = 1;
1698 int rid, i;
1699 int bFullId = find_option("full",0,0)!=0;
 
1700 verify_all_options();
1701 db_find_and_open_repository(0, 0);
1702 if( g.argc<3 ){
1703 usage("test-attachments-to-json TARGET_ID");
1704 return;
1705 }
1706 for( i = 2; i < g.argc; ++i ){
1707 const char *zTarget = g.argv[i];
1708 rid = attachment_target_rid(zTarget, bFullId);
1709 if( 0==rid ){
1710 fossil_print("** cannot resolve %s\n", zTarget);
1711 continue;
1712 }
1713 pManifest = manifest_get(rid, CFTYPE_ANY, NULL);
1714 attachments_to_json(pManifest, &b, bLatestOnly, emptyPolicy);
1715 fossil_print("Attachments for %s: ", zTarget);
1716 if( b.nUsed ){
1717 char *zPretty = db_text(0,"SELECT json_pretty(%B)", &b);
1718 fossil_print("%s\n", zPretty);
1719 fossil_free(zPretty);
1720 }else{
1721 fossil_print("none\n");
1722 }
1723 blob_reset(&b);
1724 manifest_destroy(pManifest);
1725 }
1726 }
1727

Keyboard Shortcuts

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