| | @@ -1204,10 +1204,11 @@ |
| 1204 | 1204 | Stmt q; |
| 1205 | 1205 | char *zTitle; |
| 1206 | 1206 | const char *zUuid; |
| 1207 | 1207 | int tagid; |
| 1208 | 1208 | int nChng = 0; |
| 1209 | + Blob *aLastVal = 0; |
| 1209 | 1210 | |
| 1210 | 1211 | login_check_credentials(); |
| 1211 | 1212 | if( !g.perm.Hyperlink || !g.perm.RdTkt ){ |
| 1212 | 1213 | login_needed(g.anon.Hyperlink && g.anon.RdTkt); |
| 1213 | 1214 | return; |
| | @@ -1233,10 +1234,12 @@ |
| 1233 | 1234 | } |
| 1234 | 1235 | if( P("raw")!=0 ){ |
| 1235 | 1236 | @ <h2>Raw Artifacts Associated With Ticket %h(zUuid)</h2> |
| 1236 | 1237 | }else{ |
| 1237 | 1238 | @ <h2>Artifacts Associated With Ticket %h(zUuid)</h2> |
| 1239 | + getAllTicketFields(); |
| 1240 | + aLastVal = blobarray_new(nField); |
| 1238 | 1241 | } |
| 1239 | 1242 | db_prepare(&q, |
| 1240 | 1243 | "SELECT datetime(mtime,toLocal()), objid, uuid, NULL, NULL, NULL" |
| 1241 | 1244 | " FROM event, blob" |
| 1242 | 1245 | " WHERE objid IN (SELECT rid FROM tagxref WHERE tagid=%d)" |
| | @@ -1289,11 +1292,11 @@ |
| 1289 | 1292 | @ <blockquote><pre> |
| 1290 | 1293 | @ %h(blob_str(&c)) |
| 1291 | 1294 | @ </pre></blockquote> |
| 1292 | 1295 | blob_reset(&c); |
| 1293 | 1296 | }else{ |
| 1294 | | - ticket_output_change_artifact(pTicket, "a", nChng); |
| 1297 | + ticket_output_change_artifact(pTicket, "a", nChng, aLastVal); |
| 1295 | 1298 | } |
| 1296 | 1299 | } |
| 1297 | 1300 | manifest_destroy(pTicket); |
| 1298 | 1301 | } |
| 1299 | 1302 | @ </li> |
| | @@ -1301,10 +1304,11 @@ |
| 1301 | 1304 | db_finalize(&q); |
| 1302 | 1305 | if( nChng ){ |
| 1303 | 1306 | @ </ol> |
| 1304 | 1307 | } |
| 1305 | 1308 | style_finish_page(); |
| 1309 | + if( aLastVal ) blobarray_delete(aLastVal, nField); |
| 1306 | 1310 | } |
| 1307 | 1311 | |
| 1308 | 1312 | /* |
| 1309 | 1313 | ** Return TRUE if the given BLOB contains a newline character. |
| 1310 | 1314 | */ |
| | @@ -1322,44 +1326,86 @@ |
| 1322 | 1326 | ** description of this object. |
| 1323 | 1327 | */ |
| 1324 | 1328 | void ticket_output_change_artifact( |
| 1325 | 1329 | Manifest *pTkt, /* Parsed artifact for the ticket change */ |
| 1326 | 1330 | const char *zListType, /* Which type of list */ |
| 1327 | | - int n /* Which ticket change is this */ |
| 1331 | + int n, /* Which ticket change is this */ |
| 1332 | + Blob *aLastVal /* Array of latest values for the diffs */ |
| 1328 | 1333 | ){ |
| 1329 | 1334 | int i; |
| 1330 | 1335 | if( zListType==0 ) zListType = "1"; |
| 1331 | 1336 | getAllTicketFields(); |
| 1332 | 1337 | @ <ol type="%s(zListType)"> |
| 1333 | 1338 | for(i=0; i<pTkt->nField; i++){ |
| 1334 | | - Blob val; |
| 1335 | | - const char *z, *zX; |
| 1336 | | - int id; |
| 1337 | | - z = pTkt->aField[i].zName; |
| 1338 | | - blob_set(&val, pTkt->aField[i].zValue); |
| 1339 | | - zX = z[0]=='+' ? z+1 : z; |
| 1340 | | - id = fieldId(zX); |
| 1339 | + const char *z = pTkt->aField[i].zName; |
| 1340 | + const char *zX = z[0]=='+' ? z+1 : z; |
| 1341 | + const int id = fieldId(zX); |
| 1342 | + const char *zValue = pTkt->aField[i].zValue; |
| 1343 | + const size_t nValue = strlen(zValue); |
| 1344 | + const int bLong = nValue>50 || memchr(zValue,'\n',nValue)!=NULL; |
| 1345 | + const int bCanDiff = aLastVal && id>=0 && aField[id].zBsln; |
| 1346 | + int bAppend = 0, bRegular = 0; |
| 1341 | 1347 | @ <li>\ |
| 1342 | 1348 | if( id<0 ){ |
| 1343 | 1349 | @ Untracked field %h(zX): |
| 1344 | 1350 | }else if( aField[id].mUsed==USEDBY_TICKETCHNG ){ |
| 1345 | 1351 | @ %h(zX): |
| 1346 | 1352 | }else if( n==0 ){ |
| 1347 | 1353 | @ %h(zX) initialized to: |
| 1348 | 1354 | }else if( z[0]=='+' && (aField[id].mUsed&USEDBY_TICKET)!=0 ){ |
| 1349 | 1355 | @ Appended to %h(zX): |
| 1350 | | - }else{ |
| 1351 | | - @ %h(zX) changed to: |
| 1352 | | - } |
| 1353 | | - if( blob_size(&val)>50 || contains_newline(&val) ){ |
| 1354 | | - @ <blockquote><pre class='verbatim'> |
| 1355 | | - @ %h(blob_str(&val)) |
| 1356 | | - @ </pre></blockquote></li> |
| 1357 | | - }else{ |
| 1358 | | - @ "%h(blob_str(&val))"</li> |
| 1359 | | - } |
| 1360 | | - blob_reset(&val); |
| 1356 | + bAppend = 1; |
| 1357 | + }else{ |
| 1358 | + if( !bCanDiff ){ |
| 1359 | + @ %h(zX) changed to: \ |
| 1360 | + } |
| 1361 | + bRegular = 1; |
| 1362 | + } |
| 1363 | + if( bCanDiff ){ |
| 1364 | + Blob *prev = aLastVal+id; |
| 1365 | + Blob val = BLOB_INITIALIZER; |
| 1366 | + if( nValue ){ |
| 1367 | + blob_init(&val, zValue, nValue+1); |
| 1368 | + val.nUsed--; /* makes blob_str() faster */ |
| 1369 | + } |
| 1370 | + if( bRegular && nValue && blob_buffer(prev) && blob_size(prev) ){ |
| 1371 | + Blob d = BLOB_INITIALIZER; |
| 1372 | + DiffConfig DCfg; |
| 1373 | + construct_diff_flags(1, &DCfg); |
| 1374 | + DCfg.diffFlags |= DIFF_HTML | DIFF_LINENO; |
| 1375 | + text_diff(prev, &val, &d, &DCfg); |
| 1376 | + @ %h(zX) changed as: |
| 1377 | + @ %s(blob_str(&d)) |
| 1378 | + @ </li> |
| 1379 | + blob_reset(&d); |
| 1380 | + }else if( bLong ){ |
| 1381 | + if( bRegular ){ |
| 1382 | + @ %h(zX) changed to: |
| 1383 | + } |
| 1384 | + @ <blockquote><pre class='verbatim'> |
| 1385 | + @ %h(zValue) |
| 1386 | + @ </pre></blockquote></li> |
| 1387 | + }else{ |
| 1388 | + if( bRegular ){ |
| 1389 | + @ %h(zX) changed to: \ |
| 1390 | + } |
| 1391 | + @ "%h(zValue)"</li> |
| 1392 | + } |
| 1393 | + if( blob_buffer(prev) && blob_size(prev) && !bAppend ){ |
| 1394 | + blob_truncate(prev,0); |
| 1395 | + } |
| 1396 | + if( nValue ) blob_appendb(prev, &val); |
| 1397 | + blob_reset(&val); |
| 1398 | + }else{ |
| 1399 | + if( bLong ){ |
| 1400 | + @ <blockquote><pre class='verbatim'> |
| 1401 | + @ %h(zValue) |
| 1402 | + @ </pre></blockquote></li> |
| 1403 | + }else{ |
| 1404 | + @ "%h(zValue)"</li> |
| 1405 | + } |
| 1406 | + } |
| 1361 | 1407 | } |
| 1362 | 1408 | @ </ol> |
| 1363 | 1409 | } |
| 1364 | 1410 | |
| 1365 | 1411 | /* |
| 1366 | 1412 | |