Fossil SCM

Improve <code>/tkthistory</code> page: render selected ticket fields as unified diffs.

george 2022-09-15 13:40 deltify-tkt-blobs
Commit b75a9d0fa383b5747d8c239bbcd961d6d6535c21da4065cf024a9cf8e29cfb79
+19
--- src/blob.c
+++ src/blob.c
@@ -927,10 +927,29 @@
927927
}
928928
void blobarray_reset(Blob *aBlob, int n){
929929
int i;
930930
for(i=0; i<n; i++) blob_reset(&aBlob[i]);
931931
}
932
+/*
933
+** Allocate array of n blobs and initialize each element with `empty_blob`
934
+*/
935
+Blob* blobarray_new(int n){
936
+ int i;
937
+ Blob *aBlob = fossil_malloc(sizeof(Blob)*n);
938
+ for(i=0; i<n; i++) aBlob[i] = empty_blob;
939
+ return aBlob;
940
+}
941
+/*
942
+** Free array of n blobs some of which may be empty (have NULL buffer)
943
+*/
944
+void blobarray_delete(Blob *aBlob, int n){
945
+ int i;
946
+ for(i=0; i<n; i++){
947
+ if( blob_buffer(aBlob+i) ) blob_reset(aBlob+i);
948
+ }
949
+ fossil_free(aBlob);
950
+}
932951
933952
/*
934953
** Parse a blob into space-separated tokens. Store each token in
935954
** an element of the blobarray aToken[]. aToken[] is nToken elements in
936955
** size. Return the number of tokens seen.
937956
--- src/blob.c
+++ src/blob.c
@@ -927,10 +927,29 @@
927 }
928 void blobarray_reset(Blob *aBlob, int n){
929 int i;
930 for(i=0; i<n; i++) blob_reset(&aBlob[i]);
931 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
932
933 /*
934 ** Parse a blob into space-separated tokens. Store each token in
935 ** an element of the blobarray aToken[]. aToken[] is nToken elements in
936 ** size. Return the number of tokens seen.
937
--- src/blob.c
+++ src/blob.c
@@ -927,10 +927,29 @@
927 }
928 void blobarray_reset(Blob *aBlob, int n){
929 int i;
930 for(i=0; i<n; i++) blob_reset(&aBlob[i]);
931 }
932 /*
933 ** Allocate array of n blobs and initialize each element with `empty_blob`
934 */
935 Blob* blobarray_new(int n){
936 int i;
937 Blob *aBlob = fossil_malloc(sizeof(Blob)*n);
938 for(i=0; i<n; i++) aBlob[i] = empty_blob;
939 return aBlob;
940 }
941 /*
942 ** Free array of n blobs some of which may be empty (have NULL buffer)
943 */
944 void blobarray_delete(Blob *aBlob, int n){
945 int i;
946 for(i=0; i<n; i++){
947 if( blob_buffer(aBlob+i) ) blob_reset(aBlob+i);
948 }
949 fossil_free(aBlob);
950 }
951
952 /*
953 ** Parse a blob into space-separated tokens. Store each token in
954 ** an element of the blobarray aToken[]. aToken[] is nToken elements in
955 ** size. Return the number of tokens seen.
956
+4 -1
--- src/default.css
+++ src/default.css
@@ -703,11 +703,14 @@
703703
td.difftxt ins > ins.edit {
704704
background-color: #c0c0ff;
705705
text-decoration: none;
706706
font-weight: bold;
707707
}
708
-
708
+body.tkt div.content li > table.udiff {
709
+ margin-left: 1.5em;
710
+ margin-top: 0.5em;
711
+}
709712
710713
span.modpending {
711714
color: #b03800;
712715
font-style: italic;
713716
}
714717
--- src/default.css
+++ src/default.css
@@ -703,11 +703,14 @@
703 td.difftxt ins > ins.edit {
704 background-color: #c0c0ff;
705 text-decoration: none;
706 font-weight: bold;
707 }
708
 
 
 
709
710 span.modpending {
711 color: #b03800;
712 font-style: italic;
713 }
714
--- src/default.css
+++ src/default.css
@@ -703,11 +703,14 @@
703 td.difftxt ins > ins.edit {
704 background-color: #c0c0ff;
705 text-decoration: none;
706 font-weight: bold;
707 }
708 body.tkt div.content li > table.udiff {
709 margin-left: 1.5em;
710 margin-top: 0.5em;
711 }
712
713 span.modpending {
714 color: #b03800;
715 font-style: italic;
716 }
717
+1 -1
--- src/info.c
+++ src/info.c
@@ -2828,11 +2828,11 @@
28282828
@ </blockquote>
28292829
}
28302830
28312831
@ <div class="section">Changes</div>
28322832
@ <p>
2833
- ticket_output_change_artifact(pTktChng, 0, 1);
2833
+ ticket_output_change_artifact(pTktChng, 0, 1, 0);
28342834
manifest_destroy(pTktChng);
28352835
style_finish_page();
28362836
}
28372837
28382838
28392839
--- src/info.c
+++ src/info.c
@@ -2828,11 +2828,11 @@
2828 @ </blockquote>
2829 }
2830
2831 @ <div class="section">Changes</div>
2832 @ <p>
2833 ticket_output_change_artifact(pTktChng, 0, 1);
2834 manifest_destroy(pTktChng);
2835 style_finish_page();
2836 }
2837
2838
2839
--- src/info.c
+++ src/info.c
@@ -2828,11 +2828,11 @@
2828 @ </blockquote>
2829 }
2830
2831 @ <div class="section">Changes</div>
2832 @ <p>
2833 ticket_output_change_artifact(pTktChng, 0, 1, 0);
2834 manifest_destroy(pTktChng);
2835 style_finish_page();
2836 }
2837
2838
2839
+66 -20
--- src/tkt.c
+++ src/tkt.c
@@ -1204,10 +1204,11 @@
12041204
Stmt q;
12051205
char *zTitle;
12061206
const char *zUuid;
12071207
int tagid;
12081208
int nChng = 0;
1209
+ Blob *aLastVal = 0;
12091210
12101211
login_check_credentials();
12111212
if( !g.perm.Hyperlink || !g.perm.RdTkt ){
12121213
login_needed(g.anon.Hyperlink && g.anon.RdTkt);
12131214
return;
@@ -1233,10 +1234,12 @@
12331234
}
12341235
if( P("raw")!=0 ){
12351236
@ <h2>Raw Artifacts Associated With Ticket %h(zUuid)</h2>
12361237
}else{
12371238
@ <h2>Artifacts Associated With Ticket %h(zUuid)</h2>
1239
+ getAllTicketFields();
1240
+ aLastVal = blobarray_new(nField);
12381241
}
12391242
db_prepare(&q,
12401243
"SELECT datetime(mtime,toLocal()), objid, uuid, NULL, NULL, NULL"
12411244
" FROM event, blob"
12421245
" WHERE objid IN (SELECT rid FROM tagxref WHERE tagid=%d)"
@@ -1289,11 +1292,11 @@
12891292
@ <blockquote><pre>
12901293
@ %h(blob_str(&c))
12911294
@ </pre></blockquote>
12921295
blob_reset(&c);
12931296
}else{
1294
- ticket_output_change_artifact(pTicket, "a", nChng);
1297
+ ticket_output_change_artifact(pTicket, "a", nChng, aLastVal);
12951298
}
12961299
}
12971300
manifest_destroy(pTicket);
12981301
}
12991302
@ </li>
@@ -1301,10 +1304,11 @@
13011304
db_finalize(&q);
13021305
if( nChng ){
13031306
@ </ol>
13041307
}
13051308
style_finish_page();
1309
+ if( aLastVal ) blobarray_delete(aLastVal, nField);
13061310
}
13071311
13081312
/*
13091313
** Return TRUE if the given BLOB contains a newline character.
13101314
*/
@@ -1322,44 +1326,86 @@
13221326
** description of this object.
13231327
*/
13241328
void ticket_output_change_artifact(
13251329
Manifest *pTkt, /* Parsed artifact for the ticket change */
13261330
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 */
13281333
){
13291334
int i;
13301335
if( zListType==0 ) zListType = "1";
13311336
getAllTicketFields();
13321337
@ <ol type="%s(zListType)">
13331338
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;
13411347
@ <li>\
13421348
if( id<0 ){
13431349
@ Untracked field %h(zX):
13441350
}else if( aField[id].mUsed==USEDBY_TICKETCHNG ){
13451351
@ %h(zX):
13461352
}else if( n==0 ){
13471353
@ %h(zX) initialized to:
13481354
}else if( z[0]=='+' && (aField[id].mUsed&USEDBY_TICKET)!=0 ){
13491355
@ 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
+ }
13611407
}
13621408
@ </ol>
13631409
}
13641410
13651411
/*
13661412
--- src/tkt.c
+++ src/tkt.c
@@ -1204,10 +1204,11 @@
1204 Stmt q;
1205 char *zTitle;
1206 const char *zUuid;
1207 int tagid;
1208 int nChng = 0;
 
1209
1210 login_check_credentials();
1211 if( !g.perm.Hyperlink || !g.perm.RdTkt ){
1212 login_needed(g.anon.Hyperlink && g.anon.RdTkt);
1213 return;
@@ -1233,10 +1234,12 @@
1233 }
1234 if( P("raw")!=0 ){
1235 @ <h2>Raw Artifacts Associated With Ticket %h(zUuid)</h2>
1236 }else{
1237 @ <h2>Artifacts Associated With Ticket %h(zUuid)</h2>
 
 
1238 }
1239 db_prepare(&q,
1240 "SELECT datetime(mtime,toLocal()), objid, uuid, NULL, NULL, NULL"
1241 " FROM event, blob"
1242 " WHERE objid IN (SELECT rid FROM tagxref WHERE tagid=%d)"
@@ -1289,11 +1292,11 @@
1289 @ <blockquote><pre>
1290 @ %h(blob_str(&c))
1291 @ </pre></blockquote>
1292 blob_reset(&c);
1293 }else{
1294 ticket_output_change_artifact(pTicket, "a", nChng);
1295 }
1296 }
1297 manifest_destroy(pTicket);
1298 }
1299 @ </li>
@@ -1301,10 +1304,11 @@
1301 db_finalize(&q);
1302 if( nChng ){
1303 @ </ol>
1304 }
1305 style_finish_page();
 
1306 }
1307
1308 /*
1309 ** Return TRUE if the given BLOB contains a newline character.
1310 */
@@ -1322,44 +1326,86 @@
1322 ** description of this object.
1323 */
1324 void ticket_output_change_artifact(
1325 Manifest *pTkt, /* Parsed artifact for the ticket change */
1326 const char *zListType, /* Which type of list */
1327 int n /* Which ticket change is this */
 
1328 ){
1329 int i;
1330 if( zListType==0 ) zListType = "1";
1331 getAllTicketFields();
1332 @ <ol type="%s(zListType)">
1333 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);
 
1341 @ <li>\
1342 if( id<0 ){
1343 @ Untracked field %h(zX):
1344 }else if( aField[id].mUsed==USEDBY_TICKETCHNG ){
1345 @ %h(zX):
1346 }else if( n==0 ){
1347 @ %h(zX) initialized to:
1348 }else if( z[0]=='+' && (aField[id].mUsed&USEDBY_TICKET)!=0 ){
1349 @ 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);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1361 }
1362 @ </ol>
1363 }
1364
1365 /*
1366
--- src/tkt.c
+++ src/tkt.c
@@ -1204,10 +1204,11 @@
1204 Stmt q;
1205 char *zTitle;
1206 const char *zUuid;
1207 int tagid;
1208 int nChng = 0;
1209 Blob *aLastVal = 0;
1210
1211 login_check_credentials();
1212 if( !g.perm.Hyperlink || !g.perm.RdTkt ){
1213 login_needed(g.anon.Hyperlink && g.anon.RdTkt);
1214 return;
@@ -1233,10 +1234,12 @@
1234 }
1235 if( P("raw")!=0 ){
1236 @ <h2>Raw Artifacts Associated With Ticket %h(zUuid)</h2>
1237 }else{
1238 @ <h2>Artifacts Associated With Ticket %h(zUuid)</h2>
1239 getAllTicketFields();
1240 aLastVal = blobarray_new(nField);
1241 }
1242 db_prepare(&q,
1243 "SELECT datetime(mtime,toLocal()), objid, uuid, NULL, NULL, NULL"
1244 " FROM event, blob"
1245 " WHERE objid IN (SELECT rid FROM tagxref WHERE tagid=%d)"
@@ -1289,11 +1292,11 @@
1292 @ <blockquote><pre>
1293 @ %h(blob_str(&c))
1294 @ </pre></blockquote>
1295 blob_reset(&c);
1296 }else{
1297 ticket_output_change_artifact(pTicket, "a", nChng, aLastVal);
1298 }
1299 }
1300 manifest_destroy(pTicket);
1301 }
1302 @ </li>
@@ -1301,10 +1304,11 @@
1304 db_finalize(&q);
1305 if( nChng ){
1306 @ </ol>
1307 }
1308 style_finish_page();
1309 if( aLastVal ) blobarray_delete(aLastVal, nField);
1310 }
1311
1312 /*
1313 ** Return TRUE if the given BLOB contains a newline character.
1314 */
@@ -1322,44 +1326,86 @@
1326 ** description of this object.
1327 */
1328 void ticket_output_change_artifact(
1329 Manifest *pTkt, /* Parsed artifact for the ticket change */
1330 const char *zListType, /* Which type of list */
1331 int n, /* Which ticket change is this */
1332 Blob *aLastVal /* Array of latest values for the diffs */
1333 ){
1334 int i;
1335 if( zListType==0 ) zListType = "1";
1336 getAllTicketFields();
1337 @ <ol type="%s(zListType)">
1338 for(i=0; i<pTkt->nField; i++){
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;
1347 @ <li>\
1348 if( id<0 ){
1349 @ Untracked field %h(zX):
1350 }else if( aField[id].mUsed==USEDBY_TICKETCHNG ){
1351 @ %h(zX):
1352 }else if( n==0 ){
1353 @ %h(zX) initialized to:
1354 }else if( z[0]=='+' && (aField[id].mUsed&USEDBY_TICKET)!=0 ){
1355 @ Appended to %h(zX):
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 }
1407 }
1408 @ </ol>
1409 }
1410
1411 /*
1412

Keyboard Shortcuts

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