Fossil SCM

Initial implementation to show "local (uncommitted) changes" in Fossil's UI. Not yet tested to destruction, but fails no more errors in the test-suite than trunk. Currently there are no links to the new page: manually navigate to "/local" (a variant of the "/ci" page).

graham 2020-05-28 21:39 ui-local-diff
Commit ccebe22576d1b607d428a376067ce6e7c5db4023dfda6a0751e904a201761854
1 file changed +555 -96
+555 -96
--- src/info.c
+++ src/info.c
@@ -1,5 +1,19 @@
1
+/*TODO
2
+** o Should /file behave differently for non-existent local files?
3
+** o Look at adding an "extras" option (non-added, non-ignored files).
4
+** o Look at sifting out "one line" differences from those with "diff blocks".
5
+** Perhaps reset the query and re-run, displaying only non-diff entries the
6
+** first time? Or perhaps buffer the output (probably bad idea).
7
+** o If I keep the extra <hr/> I've added after a diff-block, is there a way
8
+** to avoid the double <hr/> if the last entry has a diff-block?
9
+** o Find a place to add links to /local.
10
+** o Remove //TODO TESTING HACK TODO
11
+** ?? In hexdump_page(), should content (and downloadName?) be reset/freed?
12
+** ?? In the test fossil (\x\$Test\Fossil) there are (at time of writing) two
13
+** commits under the same artifact... is this normal?
14
+*/
115
/*
216
** Copyright (c) 2007 D. Richard Hipp
317
**
418
** This program is free software; you can redistribute it and/or
519
** modify it under the terms of the Simplified BSD License (also
@@ -322,17 +336,36 @@
322336
|TIMELINE_CHPICK,
323337
0, 0, 0, rid, rid2, 0);
324338
db_finalize(&q);
325339
}
326340
341
+/*
342
+** Read the content of file zName (prepended with the checkout directory)
343
+** and put it into the uninitialized blob. The blob is zeroed if the file
344
+** does not exist (if the file cannot be read, blob_read_from_file() aborts
345
+** the program).
346
+*/
347
+static void content_from_file(
348
+ const char *zName, /* Filename (relative to checkout) of file to be read */
349
+ Blob *pBlob /* Pointer to blob to receive contents */
350
+){
351
+ const char *zFullPath = mprintf("%s%s", g.zLocalRoot, zName);
352
+ blob_zero(pBlob);
353
+ if( file_size(zFullPath, ExtFILE)>=0 ){
354
+ blob_read_from_file(pBlob, zFullPath, ExtFILE);
355
+ }
356
+}
327357
328358
/*
329359
** Append the difference between artifacts to the output
360
+** If zLocal is not NULL, instead compare against the local
361
+** copy of the file it names in the repository.
330362
*/
331363
static void append_diff(
332364
const char *zFrom, /* Diff from this artifact */
333365
const char *zTo, /* ... to this artifact */
366
+ const char *zLocal, /* ... OR to this local file */
334367
u64 diffFlags, /* Diff formatting flags */
335368
ReCompiled *pRe /* Only show change matching this regex */
336369
){
337370
int fromid;
338371
int toid;
@@ -344,10 +377,12 @@
344377
blob_zero(&from);
345378
}
346379
if( zTo ){
347380
toid = uuid_to_rid(zTo, 0);
348381
content_get(toid, &to);
382
+ }else if( zLocal ){ /* Read the file on disk */
383
+ content_from_file(zLocal, &to);
349384
}else{
350385
blob_zero(&to);
351386
}
352387
blob_zero(&out);
353388
if( diffFlags & DIFF_SIDEBYSIDE ){
@@ -396,11 +431,11 @@
396431
}
397432
}else{
398433
@ Changes to %h(zName).
399434
}
400435
if( diffFlags ){
401
- append_diff(zOld, zNew, diffFlags, pRe);
436
+ append_diff(zOld, zNew, NULL, diffFlags, pRe);
402437
}
403438
}else{
404439
if( zOld && zNew ){
405440
if( fossil_strcmp(zOld, zNew)!=0 ){
406441
@ Modified %z(href("%R/finfo?name=%T&m=%!S",zName,zNew))%h(zName)</a>
@@ -427,18 +462,172 @@
427462
}else{
428463
@ Added %z(href("%R/finfo?name=%T&m=%!S",zName,zNew))%h(zName)</a>
429464
@ version %z(href("%R/artifact/%!S",zNew))[%S(zNew)]</a>.
430465
}
431466
if( diffFlags ){
432
- append_diff(zOld, zNew, diffFlags, pRe);
467
+ append_diff(zOld, zNew, NULL, diffFlags, pRe);
433468
}else if( zOld && zNew && fossil_strcmp(zOld,zNew)!=0 ){
434469
@ &nbsp;&nbsp;
435470
@ %z(href("%R/fdiff?v1=%!S&v2=%!S",zOld,zNew))[diff]</a>
436471
}
437472
}
438473
@ </p>
439474
}
475
+
476
+/*
477
+** Append notice of executable or symlink being gained or lost.
478
+*/
479
+static void append_status(
480
+ const char *zAction, /* Whether status was gained or lost */
481
+ const char *zStatus, /* The status that was gained/lost */
482
+ const char *zName, /* Name of file */
483
+ const char *zOld /* Existing artifact */
484
+){
485
+ if( !g.perm.Hyperlink ){
486
+ @ %h(zName) %h(zAction) %h(zStatus) status.
487
+ }else{
488
+ @ %z(href("%R/finfo?name=%T&m=%!S",zName,zOld))%h(zName)</a>
489
+ @ from %z(href("%R/artifact/%!S",zOld))[%S(zOld)]</a>
490
+ @ %h(zAction) %h(zStatus) status.
491
+ }
492
+}
493
+
494
+/*
495
+** Append web-page output that shows changes between a file at last check-in
496
+** and its current state on disk (i.e. any uncommitted changes). The status
497
+** ("changed", "missing" etc.) is a blend of that from append_file_change_line()
498
+** and diff_against_disk() (in "diffcmd.c").
499
+**
500
+** The file-differences (if being shown) use append_diff() as before, but
501
+** there is an additional parameter (zLocal) which, if non-NULL, causes it
502
+** to compare the checked-in version against the named file on disk.
503
+*/
504
+static void append_local_file_change_line(
505
+ const char *zName, /* Name of the file that has changed */
506
+ const char *zOld, /* blob.uuid before change. NULL for added files */
507
+ int isDeleted, /* Has the file-on-disk been removed from Fossil? */
508
+ int isChnged, /* Has the file changed in some way (see vfile.c) */
509
+ int isNew, /* Has the file been ADDed (but not yet committed? */
510
+ int isLink, /* Is the file a symbolic link? */
511
+ u64 diffFlags, /* Flags for text_diff(). Zero to omit diffs */
512
+ ReCompiled *pRe /* Only show diffs that match this regex, if not NULL */
513
+){
514
+ char *zFullName = mprintf("%s%s", g.zLocalRoot, zName);
515
+ int isFilePresent = !file_access(zFullName, F_OK);
516
+ int showDiff = 0;
517
+//TODO TESTING HACK TODO
518
+if( strncmp(zName,"aa",2)==0 ){
519
+ isChnged = atoi(zName+2);
520
+}
521
+//TODO TESTING HACK TODO
522
+ @ <p>
523
+ if( !g.perm.Hyperlink ){
524
+ if( isDeleted ){
525
+ if( isFilePresent ){
526
+ @ Deleted %h(zName) (still present as a local file).
527
+ showDiff = 1;
528
+ }else{
529
+ @ Deleted %h(zName).
530
+ }
531
+ }else if( isNew ){
532
+ if( isFilePresent ){
533
+ @ Added %h(zName) but not committed.
534
+ }else{
535
+ @ Missing %h(zName) (was added to checkout).
536
+ }
537
+ }else switch( isChnged ){
538
+ /*TODO
539
+ ** These "special cases" have not been properly tested (by creating
540
+ ** entries in a in a repository to trigger them), but they do display
541
+ ** as expected when "forced" to appear.
542
+ */
543
+ case 3:
544
+ @ Added %h(zName) due to a merge.
545
+ break;
546
+ case 5:
547
+ @ Added %h(zName) due to an integration merge.
548
+ break;
549
+ case 6: append_status( "gained", "executable", zName, zOld); break;
550
+ case 7: append_status( "gained", "symlink", zName, zOld); break;
551
+ case 8: append_status( "lost", "executable", zName, zOld); break;
552
+ case 9: append_status( "lost", "symlink", zName, zOld); break;
553
+
554
+ default: /* Normal edit */
555
+ @ Local changes of %h(zName).
556
+ showDiff = 1;
557
+ }
558
+ if( showDiff && diffFlags ){
559
+ append_diff(zOld, NULL, zName, diffFlags, pRe);
560
+ @ <hr/>
561
+ }
562
+ }else{
563
+ if( isDeleted ){
564
+ if( isFilePresent ){ /* DELETEd but still on disk */
565
+ @ Deleted %z(href("%R/finfo?name=%T&m=%!S",zName,zOld))%h(zName)</a>
566
+ @ from %z(href("%R/artifact/%!S",zOld))[%S(zOld)]</a> (still present
567
+ @ as %z(href("%R/file/%T?ci=ckout&annot=removed from checkout",zName))
568
+ @ [local file]</a>).
569
+ showDiff = 1;
570
+ }else{ /* DELETEd and deleted from disk */
571
+ @ Deleted %z(href("%R/finfo?name=%T&m=%!S",zName,zOld))%h(zName)</a>
572
+ @ from %z(href("%R/artifact/%!S",zOld))[%S(zOld)]</a>.
573
+ }
574
+ }else if( isNew ){
575
+ if( isFilePresent ){ /* ADDed and present on disk */
576
+ @ Added %z(href("%R/file/%T?ci=ckout",zName))%h(zName)</a>
577
+ @ but not committed.
578
+ }else{ /* ADDed but not present on disk */
579
+ @ Missing %h(zName) (was added to checkout).
580
+ }
581
+ }else switch( isChnged ){
582
+ /*TODO Not fully tested... see see no-hyperlink version above */
583
+ case 3: /* Added by a merge */
584
+ @ Added
585
+ @ %z(href("%R/file/%T?ci=ckout&annot=added by merge",zName))%h(zName)
586
+ @ </a> to %z(href("%R/artifact/%!S",zOld))[%S(zOld)]</a> due to merge.
587
+ break;
588
+ case 5: /* Added by an integration merge */
589
+ @ Added
590
+ @ %z(href("%R/file/%T?ci=ckout&annot=added by integration merge",zName))
591
+ @ %h(zName)</a> to
592
+ @ %z(href("%R/artifact/%!S",zOld))[%S(zOld)]</a> due to integrate merge.
593
+ break;
594
+ case 6: append_status( "gained", "executable", zName, zOld); break;
595
+ case 7: append_status( "gained", "symlink", zName, zOld); break;
596
+ case 8: append_status( "lost", "executable", zName, zOld); break;
597
+ case 9: append_status( "lost", "symlink", zName, zOld); break;
598
+
599
+ default: /* Normal edit */
600
+ @ Local changes of
601
+ @ %z(href("%R/finfo?name=%T&m=%!S",zName,zOld))%h(zName)</a>
602
+ @ from %z(href("%R/artifact/%!S",zOld))[%S(zOld)]</a> to
603
+ @ %z(href("%R/file/%T?ci=ckout&annot=edited locally",zName))
604
+ @ [local file]</a>
605
+ showDiff = 1;
606
+ }
607
+ if( showDiff ){
608
+ if( diffFlags ){
609
+ append_diff(zOld, NULL, zName, diffFlags, pRe);
610
+ /*TODO
611
+ ** Not related to the local-mode, but if two or more files have been
612
+ ** changed in a commit/"local changes", it is sometimes easy to miss
613
+ ** the switch from one to the other. The following (IMHO) makes things
614
+ ** clearer, but can mean there's a double rule at the bottom of the
615
+ ** page. If kept, a similar <hr/> should probably be added to
616
+ ** append_file_change_line() (but would need to check how things look
617
+ ** when called from /vinfo).
618
+ */
619
+ @ <hr/>
620
+ }else if( isChnged ){
621
+ @ &nbsp;&nbsp;
622
+ @ %z(href("%R/localdiff?name=%T",zName))[diff]</a>
623
+ }
624
+ }
625
+ }
626
+ @ </p>
627
+ fossil_free(zFullName);
628
+}
440629
441630
/*
442631
** Generate javascript to enhance HTML diffs.
443632
*/
444633
void append_diff_javascript(int sideBySide){
@@ -596,19 +785,24 @@
596785
}
597786
598787
/*
599788
** WEBPAGE: vinfo
600789
** WEBPAGE: ci
790
+** WEBPAGE: local
601791
** URL: /ci/ARTIFACTID
602792
** OR: /ci?name=ARTIFACTID
603793
**
604794
** Display information about a particular check-in. The exact
605795
** same information is shown on the /info page if the name query
606796
** parameter to /info describes a check-in.
607797
**
608798
** The ARTIFACTID can be a unique prefix for the HASH of the check-in,
609799
** or a tag or branch name that identifies the check-in.
800
+**
801
+** Use of /local (or the use of "ckout" for ARTIFACTID) will show the
802
+** same header details as /ci/tip, but then displays any (uncommitted)
803
+** edits made to files in the checkout directory.
610804
*/
611805
void ci_page(void){
612806
Stmt q1, q2, q3;
613807
int rid;
614808
int isLeaf;
@@ -621,14 +815,37 @@
621815
ReCompiled *pRe = 0; /* regex */
622816
const char *zW; /* URL param for ignoring whitespace */
623817
const char *zPage = "vinfo"; /* Page that shows diffs */
624818
const char *zPageHide = "ci"; /* Page that hides diffs */
625819
const char *zBrName; /* Branch name */
820
+ int bLocalMode; /* TRUE for /local; FALSE otherwise */
821
+ int vid; /* Virtual file system? */
626822
627823
login_check_credentials();
628824
if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
629825
zName = P("name");
826
+ /* Local mode is selected by either "/local" or with a "name" of "ckout".
827
+ ** First, check we have access to the checkout (and report to the user if we
828
+ ** don't), then refresh the "vfile" table (recording which files in the
829
+ ** checkout have changed etc.). We then change the "name" parameter to "tip"
830
+ ** so that the "header" section displays info about the check-in that the
831
+ ** checkout came from.
832
+ */
833
+ bLocalMode = (g.zPath[0]=='l') || (fossil_strcmp(zName,"ckout")==0);
834
+ if( bLocalMode ){
835
+ vid = g.localOpen ? db_lget_int("checkout", 0) : 0;
836
+ if( vid==0 ){
837
+ /*TODO Is this the right response? */
838
+ style_header("No Local Checkout");
839
+ @ No access to local checkout.
840
+ style_footer();
841
+ return;
842
+ }
843
+ vfile_check_signature(vid, CKSIG_ENOTFILE);
844
+ zName = "tip";
845
+ cgi_replace_parameter("name","tip"); /* Needed to get rid below */
846
+ }
630847
rid = name_to_rid_www("name");
631848
if( rid==0 ){
632849
style_header("Check-in Information Error");
633850
@ No such object: %h(g.argv[2])
634851
style_footer();
@@ -667,11 +884,15 @@
667884
int okWiki = 0;
668885
Blob wiki_read_links = BLOB_INITIALIZER;
669886
Blob wiki_add_links = BLOB_INITIALIZER;
670887
671888
Th_Store("current_checkin", zName);
672
- style_header("Check-in [%S]", zUuid);
889
+ if( bLocalMode ){
890
+ style_header("Local Changes from Check-in [%S]", zUuid);
891
+ }else{
892
+ style_header("Check-in [%S]", zUuid);
893
+ }
673894
login_anonymous_available();
674895
zEUser = db_text(0,
675896
"SELECT value FROM tagxref"
676897
" WHERE tagid=%d AND rid=%d AND tagtype>0",
677898
TAG_USER, rid);
@@ -873,14 +1094,25 @@
8731094
wiki_render_associated("checkin", zUuid, 0);
8741095
}
8751096
render_backlink_graph(zUuid, "<div class=\"section\">References</div>\n");
8761097
@ <div class="section">Context</div>
8771098
render_checkin_context(rid, 0, 0);
878
- @ <div class="section">Changes</div>
1099
+ if( bLocalMode ){
1100
+ @ <div class="section">Uncommitted Changes</div>
1101
+ }else{
1102
+ @ <div class="section">Changes</div>
1103
+ }
8791104
@ <div class="sectionmenu">
8801105
diffFlags = construct_diff_flags(diffType);
8811106
zW = (diffFlags&DIFF_IGNORE_ALLWS)?"&w":"";
1107
+ /* In local mode, having displayed the header info for "tip", switch zName
1108
+ ** to be "ckout" so the style-altering links (unified or side-by-side etc.)
1109
+ ** will correctly re-select local-mode.
1110
+ */
1111
+ if( bLocalMode ){
1112
+ zName = "ckout";
1113
+ }
8821114
if( diffType!=0 ){
8831115
@ %z(chref("button","%R/%s/%T?diff=0",zPageHide,zName))\
8841116
@ Hide&nbsp;Diffs</a>
8851117
}
8861118
if( diffType!=1 ){
@@ -898,48 +1130,163 @@
8981130
}else{
8991131
@ %z(chref("button","%R/%s/%T?diff=%d&w",zPage,zName,diffType))
9001132
@ Ignore&nbsp;Whitespace</a>
9011133
}
9021134
}
903
- if( zParent ){
904
- @ %z(chref("button","%R/vpatch?from=%!S&to=%!S",zParent,zUuid))
905
- @ Patch</a>
1135
+ if( bLocalMode ){
1136
+ @ %z(chref("button","%R/localpatch")) Patch</a>
1137
+ }else{
1138
+ if( zParent ){
1139
+ @ %z(chref("button","%R/vpatch?from=%!S&to=%!S",zParent,zUuid))
1140
+ @ Patch</a>
1141
+ }
9061142
}
9071143
if( g.perm.Admin ){
9081144
@ %z(chref("button","%R/mlink?ci=%!S",zUuid))MLink Table</a>
9091145
}
9101146
@</div>
9111147
if( pRe ){
9121148
@ <p><b>Only differences that match regular expression "%h(zRe)"
9131149
@ are shown.</b></p>
9141150
}
915
- db_prepare(&q3,
916
- "SELECT name,"
917
- " mperm,"
918
- " (SELECT uuid FROM blob WHERE rid=mlink.pid),"
919
- " (SELECT uuid FROM blob WHERE rid=mlink.fid),"
920
- " (SELECT name FROM filename WHERE filename.fnid=mlink.pfnid)"
921
- " FROM mlink JOIN filename ON filename.fnid=mlink.fnid"
922
- " WHERE mlink.mid=%d AND NOT mlink.isaux"
923
- " AND (mlink.fid>0"
924
- " OR mlink.fnid NOT IN (SELECT pfnid FROM mlink WHERE mid=%d))"
925
- " ORDER BY name /*sort*/",
926
- rid, rid
927
- );
928
- while( db_step(&q3)==SQLITE_ROW ){
929
- const char *zName = db_column_text(&q3,0);
930
- int mperm = db_column_int(&q3, 1);
931
- const char *zOld = db_column_text(&q3,2);
932
- const char *zNew = db_column_text(&q3,3);
933
- const char *zOldName = db_column_text(&q3, 4);
934
- append_file_change_line(zName, zOld, zNew, zOldName, diffFlags,pRe,mperm);
935
- }
936
- db_finalize(&q3);
1151
+ if( bLocalMode ){
1152
+ /* Following SQL taken from diff_against_disk() in diffcmd.c */
1153
+ db_begin_transaction();
1154
+ db_prepare(&q3,
1155
+ "SELECT pathname, deleted, chnged , rid==0, rid, islink"
1156
+ " FROM vfile"
1157
+ " WHERE vid=%d"
1158
+ " AND (deleted OR chnged OR rid==0)"
1159
+ " ORDER BY pathname /*scan*/",
1160
+ vid
1161
+ );
1162
+ /* TODO Have the option of showing "extras" (non-ignored files in the
1163
+ ** checkout directory that have not been ADDed). If done, they should
1164
+ ** be ahead of any potential "diff-blocks" so they don't get lost
1165
+ ** (which is the inspiration for...)
1166
+ ** TODO Consider making this two-pass, where the first pass skips anything
1167
+ ** that would show a diff-block (and the second pass only shows such
1168
+ ** entries). This would group all "one-line" entries at the top so
1169
+ ** they are less likely to be missed.
1170
+ ** TODO Possibly (at some stage) have an option to commit?
1171
+ */
1172
+ while( db_step(&q3)==SQLITE_ROW ){
1173
+ const char *zPathname = db_column_text(&q3,0);
1174
+ int isDeleted = db_column_int(&q3, 1);
1175
+ int isChnged = db_column_int(&q3,2);
1176
+ int isNew = db_column_int(&q3,3);
1177
+ int srcid = db_column_int(&q3, 4);
1178
+ int isLink = db_column_int(&q3, 5);
1179
+ char *zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", srcid);
1180
+ append_local_file_change_line(zPathname, zUuid,
1181
+ isDeleted, isChnged, isNew, isLink, diffFlags,pRe);
1182
+ free(zUuid);
1183
+ }
1184
+ db_finalize(&q3);
1185
+ db_end_transaction(1); /* ROLLBACK */
1186
+ }else{ /* Normal, non-local-mode: show diffs against parent */
1187
+ db_prepare(&q3,
1188
+ "SELECT name,"
1189
+ " mperm,"
1190
+ " (SELECT uuid FROM blob WHERE rid=mlink.pid),"
1191
+ " (SELECT uuid FROM blob WHERE rid=mlink.fid),"
1192
+ " (SELECT name FROM filename WHERE filename.fnid=mlink.pfnid)"
1193
+ " FROM mlink JOIN filename ON filename.fnid=mlink.fnid"
1194
+ " WHERE mlink.mid=%d AND NOT mlink.isaux"
1195
+ " AND (mlink.fid>0"
1196
+ " OR mlink.fnid NOT IN (SELECT pfnid FROM mlink WHERE mid=%d))"
1197
+ " ORDER BY name /*sort*/",
1198
+ rid, rid
1199
+ );
1200
+ while( db_step(&q3)==SQLITE_ROW ){
1201
+ const char *zName = db_column_text(&q3,0);
1202
+ int mperm = db_column_int(&q3, 1);
1203
+ const char *zOld = db_column_text(&q3,2);
1204
+ const char *zNew = db_column_text(&q3,3);
1205
+ const char *zOldName = db_column_text(&q3, 4);
1206
+ append_file_change_line(zName, zOld, zNew, zOldName, diffFlags,pRe,mperm);
1207
+ }
1208
+ db_finalize(&q3);
1209
+ }
9371210
append_diff_javascript(diffType==2);
9381211
cookie_render();
9391212
style_footer();
9401213
}
1214
+
1215
+/*
1216
+** WEBPAGE: localpatch
1217
+** URL: /localpatch
1218
+**
1219
+** Shows a patch from the current checkout, incorporating any
1220
+** uncommitted local edits.
1221
+*/
1222
+void localpatch_page(void){
1223
+ Stmt q3;
1224
+ int vid;
1225
+
1226
+ login_check_credentials();
1227
+ if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
1228
+
1229
+ vid = g.localOpen ? db_lget_int("checkout", 0) : 0;
1230
+ if( vid==0 ){
1231
+ /*TODO Is this the right response? */
1232
+ style_header("No Local Checkout");
1233
+ @ No access to local checkout.
1234
+ style_footer();
1235
+ return;
1236
+ }
1237
+ vfile_check_signature(vid, CKSIG_ENOTFILE);
1238
+
1239
+ cgi_set_content_type("text/plain");
1240
+
1241
+ db_begin_transaction();
1242
+ /*TODO
1243
+ ** This query is the same as in ci_page() for local-mode (as well as in
1244
+ ** diff_against_disk() in diffcmd.c, where it was originally taken from).
1245
+ ** Should they be "coalesced" in some way?
1246
+ */
1247
+ db_prepare(&q3,
1248
+ "SELECT pathname, deleted, chnged , rid==0, rid, islink"
1249
+ " FROM vfile"
1250
+ " WHERE vid=%d"
1251
+ " AND (deleted OR chnged OR rid==0)"
1252
+ " ORDER BY pathname /*scan*/",
1253
+ vid
1254
+ );
1255
+ while( db_step(&q3)==SQLITE_ROW ){
1256
+ const char *zPathname = db_column_text(&q3,0);
1257
+ int isDeleted = db_column_int(&q3, 1);
1258
+ int isChnged = db_column_int(&q3,2);
1259
+ int isNew = db_column_int(&q3,3);
1260
+ int srcid = db_column_int(&q3, 4);
1261
+ int isLink = db_column_int(&q3, 5);
1262
+ char *zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", srcid);
1263
+
1264
+ if( isChnged ){
1265
+ Blob c1, c2; /* Content to diff */
1266
+ Blob out; /* Diff output text */
1267
+ int diffFlags = 4;
1268
+
1269
+ content_get(srcid, &c1);
1270
+ content_from_file(zPathname, &c2);
1271
+ blob_zero(&out);
1272
+ text_diff(&c1, &c2, &out, 0, diffFlags);
1273
+ blob_reset(&c1);
1274
+ blob_reset(&c2);
1275
+ if( blob_size(&out) ){
1276
+ diff_print_index(zPathname, diffFlags);
1277
+ diff_print_filenames(zPathname, zPathname, diffFlags);
1278
+ fossil_print("%s\n", blob_str(&out));
1279
+ }
1280
+ /* Release memory resources */
1281
+ blob_reset(&out);
1282
+ }
1283
+ free(zUuid);
1284
+ }
1285
+ db_finalize(&q3);
1286
+ db_end_transaction(1); /* ROLLBACK */
1287
+}
9411288
9421289
/*
9431290
** WEBPAGE: winfo
9441291
** URL: /winfo?name=UUID
9451292
**
@@ -1216,11 +1563,11 @@
12161563
style_submenu_element("Side-by-Side Diff",
12171564
"%R/vdiff?%s&diff=2%s%T%s",
12181565
zQuery,
12191566
zGlob ? "&glob=" : "", zGlob ? zGlob : "", zW);
12201567
}
1221
- if( diffType!=1 ) {
1568
+ if( diffType!=1 ){
12221569
style_submenu_element("Unified Diff",
12231570
"%R/vdiff?%s&diff=1%s%T%s",
12241571
zQuery,
12251572
zGlob ? "&glob=" : "", zGlob ? zGlob : "", zW);
12261573
}
@@ -1400,11 +1747,11 @@
14001747
}
14011748
cnt++;
14021749
continue;
14031750
}
14041751
if( !sameFilename ){
1405
- if( prevName && showDetail ) {
1752
+ if( prevName && showDetail ){
14061753
@ </ul>
14071754
}
14081755
if( mPerm==PERM_LNK ){
14091756
@ <li>Symbolic link
14101757
objType |= OBJTYPE_SYMLINK;
@@ -1520,11 +1867,11 @@
15201867
objType |= OBJTYPE_TICKET;
15211868
}else if( zType[0]=='c' ){
15221869
@ Manifest of check-in
15231870
objType |= OBJTYPE_CHECKIN;
15241871
}else if( zType[0]=='e' ){
1525
- if( eventTagId != 0) {
1872
+ if( eventTagId != 0){
15261873
@ Instance of technote
15271874
objType |= OBJTYPE_EVENT;
15281875
hyperlink_to_event_tagid(db_column_int(&q, 5));
15291876
}else{
15301877
@ Attachment to technote
@@ -1567,11 +1914,11 @@
15671914
}else{
15681915
@ Attachment "%h(zFilename)" to
15691916
}
15701917
objType |= OBJTYPE_ATTACHMENT;
15711918
if( fossil_is_uuid(zTarget) ){
1572
- if ( db_exists("SELECT 1 FROM tag WHERE tagname='tkt-%q'",
1919
+ if( db_exists("SELECT 1 FROM tag WHERE tagname='tkt-%q'",
15731920
zTarget)
15741921
){
15751922
if( g.perm.Hyperlink && g.anon.RdTkt ){
15761923
@ ticket [%z(href("%R/tktview?name=%!S",zTarget))%S(zTarget)</a>]
15771924
}else{
@@ -1625,11 +1972,13 @@
16251972
}
16261973
16271974
16281975
/*
16291976
** WEBPAGE: fdiff
1977
+** WEBPAGE: localdiff
16301978
** URL: fdiff?v1=UUID&v2=UUID
1979
+** URL: localdiff?name=filename
16311980
**
16321981
** Two arguments, v1 and v2, identify the artifacts to be diffed.
16331982
** Show diff side by side unless sbs is 0. Generate plain text if
16341983
** "patch" is present, otherwise generate "pretty" HTML.
16351984
**
@@ -1637,10 +1986,14 @@
16371986
**
16381987
** If the "from" and "to" query parameters are both present, then they are
16391988
** the names of two files within the check-in "ci" that are diffed. If the
16401989
** "ci" parameter is omitted, then the most recent check-in ("tip") is
16411990
** used.
1991
+**
1992
+** The /localdiff version will diff the given filename from the most recent
1993
+** check-in ("tip") against the current (edited) version in the checkout
1994
+** directory.
16421995
**
16431996
** Additional parameters:
16441997
**
16451998
** dc=N Show N lines of context around each diff
16461999
** patch Use the patch diff format
@@ -1658,17 +2011,23 @@
16582011
const char *zRe;
16592012
ReCompiled *pRe = 0;
16602013
u64 diffFlags;
16612014
u32 objdescFlags = 0;
16622015
int verbose = PB("verbose");
2016
+ int bLocalMode = g.zPath[0]=='l'; /* diff against checkout */
2017
+ const char *zLocalName = NULL; /* Holds local filename */
16632018
16642019
login_check_credentials();
16652020
if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
16662021
cookie_link_parameter("diff","diff","2");
16672022
diffType = atoi(PD("diff","2"));
16682023
cookie_render();
1669
- if( P("from") && P("to") ){
2024
+ if( bLocalMode ){
2025
+ zLocalName = P("name");
2026
+ v1 = artifact_from_ci_and_filename("name");
2027
+ v2 = (zLocalName!=NULL)?-1:0; /* -1 prevents "not found" check below */
2028
+ }else if( P("from") && P("to") ){
16702029
v1 = artifact_from_ci_and_filename("from");
16712030
v2 = artifact_from_ci_and_filename("to");
16722031
}else{
16732032
Stmt q;
16742033
v1 = name_to_rid_www("v1");
@@ -1707,14 +2066,19 @@
17072066
if( zRe ) re_compile(&pRe, zRe, 0);
17082067
if( verbose ) objdescFlags |= OBJDESC_DETAIL;
17092068
if( isPatch ){
17102069
Blob c1, c2, *pOut;
17112070
pOut = cgi_output_blob();
2071
+
17122072
cgi_set_content_type("text/plain");
17132073
diffFlags = 4;
17142074
content_get(v1, &c1);
1715
- content_get(v2, &c2);
2075
+ if( bLocalMode ){
2076
+ content_from_file(zLocalName, &c2);
2077
+ }else{
2078
+ content_get(v2, &c2);
2079
+ }
17162080
text_diff(&c1, &c2, pOut, pRe, diffFlags);
17172081
blob_reset(&c1);
17182082
blob_reset(&c2);
17192083
return;
17202084
}
@@ -1723,38 +2087,62 @@
17232087
zV2 = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", v2);
17242088
diffFlags = construct_diff_flags(diffType) | DIFF_HTML;
17252089
17262090
style_header("Diff");
17272091
style_submenu_checkbox("w", "Ignore Whitespace", 0, 0);
1728
- if( diffType==2 ){
1729
- style_submenu_element("Unified Diff", "%R/fdiff?v1=%T&v2=%T&diff=1",
1730
- P("v1"), P("v2"));
1731
- }else{
1732
- style_submenu_element("Side-by-side Diff", "%R/fdiff?v1=%T&v2=%T&diff=2",
1733
- P("v1"), P("v2"));
2092
+ if( bLocalMode ){
2093
+ if( diffType==2 ){
2094
+ style_submenu_element("Unified Diff", "%R/localdiff?name=%T&diff=1",
2095
+ zLocalName);
2096
+ }else{
2097
+ style_submenu_element("Side-by-side Diff", "%R/localdiff?name=%T&diff=2",
2098
+ zLocalName);
2099
+ }
2100
+ }else{ /* Normal */
2101
+ if( diffType==2 ){
2102
+ style_submenu_element("Unified Diff", "%R/fdiff?v1=%T&v2=%T&diff=1",
2103
+ P("v1"), P("v2"));
2104
+ }else{
2105
+ style_submenu_element("Side-by-side Diff", "%R/fdiff?v1=%T&v2=%T&diff=2",
2106
+ P("v1"), P("v2"));
2107
+ }
17342108
}
17352109
style_submenu_checkbox("verbose", "Verbose", 0, 0);
1736
- style_submenu_element("Patch", "%R/fdiff?v1=%T&v2=%T&patch",
1737
- P("v1"), P("v2"));
2110
+ if( bLocalMode ){
2111
+ style_submenu_element("Patch", "%R/localdiff?name=%T&patch", zLocalName);
2112
+ }else{
2113
+ style_submenu_element("Patch", "%R/fdiff?v1=%T&v2=%T&patch",
2114
+ P("v1"), P("v2"));
2115
+ }
17382116
17392117
if( P("smhdr")!=0 ){
17402118
@ <h2>Differences From Artifact
17412119
@ %z(href("%R/artifact/%!S",zV1))[%S(zV1)]</a> To
1742
- @ %z(href("%R/artifact/%!S",zV2))[%S(zV2)]</a>.</h2>
2120
+ if( bLocalMode ){
2121
+ @ %z(href("%R/local"))[Local Changes]</a> of
2122
+ @ %z(href("%R/file/%T?ci=ckout",zLocalName))%h(zLocalName)</a>.
2123
+ }else{
2124
+ @ %z(href("%R/artifact/%!S",zV2))[%S(zV2)]</a>.</h2>
2125
+ }
17432126
}else{
17442127
@ <h2>Differences From
17452128
@ Artifact %z(href("%R/artifact/%!S",zV1))[%S(zV1)]</a>:</h2>
17462129
object_description(v1, objdescFlags,0, 0);
1747
- @ <h2>To Artifact %z(href("%R/artifact/%!S",zV2))[%S(zV2)]</a>:</h2>
1748
- object_description(v2, objdescFlags,0, 0);
2130
+ if( bLocalMode ){
2131
+ @ <h2>To %z(href("%R/local"))[Local Changes]</a>
2132
+ @ of %z(href("%R/file/%T?ci=ckout",zLocalName))%h(zLocalName)</a>.</h2>
2133
+ }else{
2134
+ @ <h2>To Artifact %z(href("%R/artifact/%!S",zV2))[%S(zV2)]</a>:</h2>
2135
+ object_description(v2, objdescFlags,0, 0);
2136
+ }
17492137
}
17502138
if( pRe ){
17512139
@ <b>Only differences that match regular expression "%h(zRe)"
17522140
@ are shown.</b>
17532141
}
17542142
@ <hr />
1755
- append_diff(zV1, zV2, diffFlags, pRe);
2143
+ append_diff(zV1, zV2, zLocalName, diffFlags, pRe);
17562144
append_diff_javascript(diffType);
17572145
style_footer();
17582146
}
17592147
17602148
/*
@@ -1908,13 +2296,16 @@
19082296
}
19092297
19102298
/*
19112299
** WEBPAGE: hexdump
19122300
** URL: /hexdump?name=ARTIFACTID
2301
+** URL: /hexdump?local=FILENAME
19132302
**
19142303
** Show the complete content of a file identified by ARTIFACTID
19152304
** as preformatted text.
2305
+**
2306
+** The second version does the same for FILENAME from the local checkout.
19162307
**
19172308
** Other parameters:
19182309
**
19192310
** verbose Show more detail when describing the object
19202311
*/
@@ -1922,42 +2313,61 @@
19222313
int rid;
19232314
Blob content;
19242315
Blob downloadName;
19252316
char *zUuid;
19262317
u32 objdescFlags = 0;
2318
+ const char *zLocalName = P("local");
2319
+ int bLocalMode = zLocalName!=NULL;
19272320
19282321
rid = name_to_rid_www("name");
19292322
login_check_credentials();
19302323
if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
1931
- if( rid==0 ) fossil_redirect_home();
1932
- if( g.perm.Admin ){
1933
- const char *zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", rid);
1934
- if( db_exists("SELECT 1 FROM shun WHERE uuid=%Q", zUuid) ){
1935
- style_submenu_element("Unshun", "%s/shun?accept=%s&sub=1#delshun",
1936
- g.zTop, zUuid);
1937
- }else{
1938
- style_submenu_element("Shun", "%s/shun?shun=%s#addshun", g.zTop, zUuid);
2324
+ if( !bLocalMode ){
2325
+ if( rid==0 ) fossil_redirect_home();
2326
+ if( g.perm.Admin ){
2327
+ const char *zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d",rid);
2328
+ if( db_exists("SELECT 1 FROM shun WHERE uuid=%Q", zUuid) ){
2329
+ style_submenu_element("Unshun", "%s/shun?accept=%s&sub=1#delshun",
2330
+ g.zTop, zUuid);
2331
+ }else{
2332
+ style_submenu_element("Shun", "%s/shun?shun=%s#addshun", g.zTop, zUuid);
2333
+ }
19392334
}
19402335
}
19412336
style_header("Hex Artifact Content");
1942
- zUuid = db_text("?","SELECT uuid FROM blob WHERE rid=%d", rid);
1943
- @ <h2>Artifact
1944
- style_copy_button(1, "hash-ar", 0, 2, "%s", zUuid);
1945
- if( g.perm.Setup ){
1946
- @ (%d(rid)):</h2>
2337
+ /* TODO
2338
+ ** Could the call to style_header() be moved so these two exclusion
2339
+ ** blocks could be merged? I don't think any of them make sense for
2340
+ ** a local file.
2341
+ */
2342
+ if( !bLocalMode ){
2343
+ zUuid = db_text("?","SELECT uuid FROM blob WHERE rid=%d", rid);
2344
+ @ <h2>Artifact
2345
+ style_copy_button(1, "hash-ar", 0, 2, "%s", zUuid);
2346
+ if( g.perm.Setup ){
2347
+ @ (%d(rid)):</h2>
2348
+ }else{
2349
+ @ :</h2>
2350
+ }
2351
+ blob_zero(&downloadName);
2352
+ if( P("verbose")!=0 ) objdescFlags |= OBJDESC_DETAIL;
2353
+ object_description(rid, objdescFlags, 0, &downloadName);
2354
+ style_submenu_element("Download", "%R/raw/%s?at=%T",
2355
+ zUuid, file_tail(blob_str(&downloadName)));
19472356
}else{
1948
- @ :</h2>
2357
+ @ <h2>File %z(href("%R/finfo?name=%T&m=tip",zLocalName))%h(zLocalName)</a>
2358
+ @ from %z(href("%R/local"))[Local Changes]</a></h2>
19492359
}
1950
- blob_zero(&downloadName);
1951
- if( P("verbose")!=0 ) objdescFlags |= OBJDESC_DETAIL;
1952
- object_description(rid, objdescFlags, 0, &downloadName);
1953
- style_submenu_element("Download", "%R/raw/%s?at=%T",
1954
- zUuid, file_tail(blob_str(&downloadName)));
19552360
@ <hr />
1956
- content_get(rid, &content);
2361
+ if( bLocalMode ){
2362
+ content_from_file(zLocalName, &content);
2363
+ }else{
2364
+ content_get(rid, &content);
2365
+ }
19572366
@ <blockquote><pre>
19582367
hexdump(&content);
2368
+ /* TODO: Should content (and downloadName?) be reset/freed? */
19592369
@ </pre></blockquote>
19602370
style_footer();
19612371
}
19622372
19632373
/*
@@ -2131,10 +2541,13 @@
21312541
** if name= cannot be understood as a hash, a default "tip" value is
21322542
** used for ci=.
21332543
**
21342544
** For /file, name= can only be interpreted as a filename. As before,
21352545
** a default value of "tip" is used for ci= if ci= is omitted.
2546
+**
2547
+** If ci=ckout then display the content of the file NAME in the local
2548
+** checkout directory.
21362549
*/
21372550
void artifact_page(void){
21382551
int rid = 0;
21392552
Blob content;
21402553
const char *zMime;
@@ -2153,10 +2566,11 @@
21532566
HQuery url;
21542567
char *zCIUuid = 0;
21552568
int isSymbolicCI = 0; /* ci= exists and is a symbolic name, not a hash */
21562569
int isBranchCI = 0; /* ci= refers to a branch name */
21572570
char *zHeader = 0;
2571
+ int bLocalMode = 0; /* TRUE if trying to show file in local checkout */
21582572
21592573
login_check_credentials();
21602574
if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
21612575
21622576
/* Capture and normalize the name= and ci= query parameters */
@@ -2200,11 +2614,14 @@
22002614
** name= as a hash for /artifact and /whatis. But for not for /file.
22012615
** For /file, a name= without a ci= while prefer to use the default
22022616
** "tip" value for ci=. */
22032617
rid = name_to_rid(zName);
22042618
}
2205
- if( rid==0 ){
2619
+ if( fossil_strcmp(zCI,"ckout")==0 ){
2620
+ bLocalMode = 1;
2621
+ rid = -1; /* Dummy value to make it look found */
2622
+ }else if( rid==0 ){
22062623
rid = artifact_from_ci_and_filename(0);
22072624
}
22082625
22092626
if( rid==0 ){ /* Artifact not found */
22102627
if( isFile ){
@@ -2237,11 +2654,25 @@
22372654
}
22382655
zUuid = db_text("?", "SELECT uuid FROM blob WHERE rid=%d", rid);
22392656
22402657
asText = P("txt")!=0;
22412658
if( isFile ){
2242
- if( zCI==0 || fossil_strcmp(zCI,"tip")==0 ){
2659
+ if( bLocalMode ){
2660
+ /*TODO
2661
+ ** Is this the best way of handling annotations to the description?
2662
+ ** If "annot=message" is part of the URL, the message is appended
2663
+ ** to the description of the file. Only used for "local" files to
2664
+ ** distinguish such files from part of the repository.
2665
+ */
2666
+ const char *annot = P("annot");
2667
+ @ <h2>File %z(href("%R/finfo?name=%T&m=tip",zName))%h(zName)</a>
2668
+ @ from %z(href("%R/local"))[Local Changes]</a>
2669
+ if( annot ){
2670
+ @ (%h(annot))
2671
+ }
2672
+ @ </h2>
2673
+ }else if( zCI==0 || fossil_strcmp(zCI,"tip")==0 ){
22432674
zCI = "tip";
22442675
isSymbolicCI = 1; /* Mark default-to-"tip" as symbolic */
22452676
@ <h2>File %z(href("%R/finfo?name=%T&m=tip",zName))%h(zName)</a>
22462677
@ from the %z(href("%R/info/tip"))latest check-in</a></h2>
22472678
}else{
@@ -2258,15 +2689,17 @@
22582689
}else{
22592690
@ as of check-in [%z(href("%R/info/%!S",zCIUuid))%S(zCIUuid)</a>]</h2>
22602691
}
22612692
blob_reset(&path);
22622693
}
2263
- style_submenu_element("Artifact", "%R/artifact/%S", zUuid);
2264
- style_submenu_element("Annotate", "%R/annotate?filename=%T&checkin=%T",
2265
- zName, zCI);
2266
- style_submenu_element("Blame", "%R/blame?filename=%T&checkin=%T",
2267
- zName, zCI);
2694
+ if( !bLocalMode ){
2695
+ style_submenu_element("Artifact", "%R/artifact/%S", zUuid);
2696
+ style_submenu_element("Annotate", "%R/annotate?filename=%T&checkin=%T",
2697
+ zName, zCI);
2698
+ style_submenu_element("Blame", "%R/blame?filename=%T&checkin=%T",
2699
+ zName, zCI);
2700
+ }
22682701
blob_init(&downloadName, zName, -1);
22692702
objType = OBJTYPE_CONTENT;
22702703
}else{
22712704
@ <h2>Artifact
22722705
style_copy_button(1, "hash-ar", 0, 2, "%s", zUuid);
@@ -2278,29 +2711,33 @@
22782711
blob_zero(&downloadName);
22792712
if( asText ) objdescFlags &= ~OBJDESC_BASE;
22802713
objType = object_description(rid, objdescFlags,
22812714
(isFile?zName:0), &downloadName);
22822715
}
2283
- if( !descOnly && P("download")!=0 ){
2284
- cgi_redirectf("%R/raw/%s?at=%T",
2285
- db_text("x", "SELECT uuid FROM blob WHERE rid=%d", rid),
2286
- file_tail(blob_str(&downloadName)));
2287
- /*NOTREACHED*/
2288
- }
2289
- if( g.perm.Admin ){
2290
- const char *zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", rid);
2291
- if( db_exists("SELECT 1 FROM shun WHERE uuid=%Q", zUuid) ){
2292
- style_submenu_element("Unshun", "%s/shun?accept=%s&sub=1#accshun",
2293
- g.zTop, zUuid);
2294
- }else{
2295
- style_submenu_element("Shun", "%s/shun?shun=%s#addshun", g.zTop, zUuid);
2716
+ if( !bLocalMode ){
2717
+ if( !descOnly && P("download")!=0 ){
2718
+ cgi_redirectf("%R/raw/%s?at=%T",
2719
+ db_text("x", "SELECT uuid FROM blob WHERE rid=%d", rid),
2720
+ file_tail(blob_str(&downloadName)));
2721
+ /*NOTREACHED*/
2722
+ }
2723
+ if( g.perm.Admin ){
2724
+ const char *zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d",rid);
2725
+ if( db_exists("SELECT 1 FROM shun WHERE uuid=%Q", zUuid) ){
2726
+ style_submenu_element("Unshun", "%s/shun?accept=%s&sub=1#accshun",
2727
+ g.zTop, zUuid);
2728
+ }else{
2729
+ style_submenu_element("Shun", "%s/shun?shun=%s#addshun", g.zTop, zUuid);
2730
+ }
22962731
}
22972732
}
22982733
22992734
if( isFile ){
23002735
if( isSymbolicCI ){
23012736
zHeader = mprintf("%s at %s", file_tail(zName), zCI);
2737
+ }else if( bLocalMode ){
2738
+ zHeader = mprintf("%s (local changes)", file_tail(zName));
23022739
}else if( zCI ){
23032740
zHeader = mprintf("%s at [%S]", file_tail(zName), zCIUuid);
23042741
}else{
23052742
zHeader = mprintf("%s", file_tail(zName));
23062743
}
@@ -2326,13 +2763,17 @@
23262763
const char *zIp = db_column_text(&q,2);
23272764
@ <p>Received on %s(zDate) from %h(zUser) at %h(zIp).</p>
23282765
}
23292766
db_finalize(&q);
23302767
}
2331
- style_submenu_element("Download", "%R/raw/%s?at=%T", zUuid, file_tail(zName));
2332
- if( db_exists("SELECT 1 FROM mlink WHERE fid=%d", rid) ){
2333
- style_submenu_element("Check-ins Using", "%R/timeline?n=200&uf=%s", zUuid);
2768
+ if( !bLocalMode ){
2769
+ style_submenu_element("Download", "%R/raw/%s?at=%T",
2770
+ zUuid, file_tail(zName));
2771
+ if( db_exists("SELECT 1 FROM mlink WHERE fid=%d", rid) ){
2772
+ style_submenu_element("Check-ins Using", "%R/timeline?n=200&uf=%s",
2773
+ zUuid);
2774
+ }
23342775
}
23352776
zMime = mimetype_from_name(blob_str(&downloadName));
23362777
if( zMime ){
23372778
if( fossil_strcmp(zMime, "text/html")==0 ){
23382779
if( asText ){
@@ -2348,24 +2789,38 @@
23482789
}else{
23492790
renderAsWiki = 1;
23502791
style_submenu_element("Text", "%s", url_render(&url, "txt", "1", 0, 0));
23512792
}
23522793
}
2353
- if( fileedit_is_editable(zName) ){
2354
- style_submenu_element("Edit",
2355
- "%R/fileedit?filename=%T&checkin=%!S",
2356
- zName, zCI);
2794
+ if( !bLocalMode ){ /* This way madness lies... */
2795
+ if( fileedit_is_editable(zName) ){
2796
+ style_submenu_element("Edit",
2797
+ "%R/fileedit?filename=%T&checkin=%!S",
2798
+ zName, zCI);
2799
+ }
23572800
}
23582801
}
23592802
if( (objType & (OBJTYPE_WIKI|OBJTYPE_TICKET))!=0 ){
23602803
style_submenu_element("Parsed", "%R/info/%s", zUuid);
23612804
}
23622805
if( descOnly ){
23632806
style_submenu_element("Content", "%R/artifact/%s", zUuid);
23642807
}else{
23652808
@ <hr />
2366
- content_get(rid, &content);
2809
+ if( bLocalMode ){
2810
+ /*TODO
2811
+ ** Should we handle non-existent local files differently? Currently,
2812
+ ** they are shown the same as if the file was present but empty. This
2813
+ ** should never happen through "normal" operation, but someone might
2814
+ ** craft a link to one. Perhaps have content_from_file() perform an
2815
+ ** existence-check (rather than relying on blob_read_from_file() which
2816
+ ** it calls returning an empty blob)?
2817
+ */
2818
+ content_from_file(zName, &content);
2819
+ }else{
2820
+ content_get(rid, &content);
2821
+ }
23672822
if( renderAsWiki ){
23682823
wiki_render_by_mimetype(&content, zMime);
23692824
}else if( renderAsHtml ){
23702825
@ <iframe src="%R/raw/%s(zUuid)"
23712826
@ width="100%%" frameborder="0" marginwidth="0" marginheight="0"
@@ -2377,11 +2832,15 @@
23772832
@ this.height=this.contentDocument.documentElement.scrollHeight + 75;
23782833
@ }
23792834
@ );
23802835
@ </script>
23812836
}else{
2382
- style_submenu_element("Hex", "%s/hexdump?name=%s", g.zTop, zUuid);
2837
+ if( bLocalMode ){
2838
+ style_submenu_element("Hex", "%R/hexdump?local=%s", zName);
2839
+ }else{
2840
+ style_submenu_element("Hex", "%s/hexdump?name=%s", g.zTop, zUuid);
2841
+ }
23832842
if( zLn==0 || atoi(zLn)==0 ){
23842843
style_submenu_checkbox("ln", "Line Numbers", 0, 0);
23852844
}
23862845
blob_to_utf8_no_bom(&content, 0);
23872846
zMime = mimetype_from_content(&content);
23882847
--- src/info.c
+++ src/info.c
@@ -1,5 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1 /*
2 ** Copyright (c) 2007 D. Richard Hipp
3 **
4 ** This program is free software; you can redistribute it and/or
5 ** modify it under the terms of the Simplified BSD License (also
@@ -322,17 +336,36 @@
322 |TIMELINE_CHPICK,
323 0, 0, 0, rid, rid2, 0);
324 db_finalize(&q);
325 }
326
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
327
328 /*
329 ** Append the difference between artifacts to the output
 
 
330 */
331 static void append_diff(
332 const char *zFrom, /* Diff from this artifact */
333 const char *zTo, /* ... to this artifact */
 
334 u64 diffFlags, /* Diff formatting flags */
335 ReCompiled *pRe /* Only show change matching this regex */
336 ){
337 int fromid;
338 int toid;
@@ -344,10 +377,12 @@
344 blob_zero(&from);
345 }
346 if( zTo ){
347 toid = uuid_to_rid(zTo, 0);
348 content_get(toid, &to);
 
 
349 }else{
350 blob_zero(&to);
351 }
352 blob_zero(&out);
353 if( diffFlags & DIFF_SIDEBYSIDE ){
@@ -396,11 +431,11 @@
396 }
397 }else{
398 @ Changes to %h(zName).
399 }
400 if( diffFlags ){
401 append_diff(zOld, zNew, diffFlags, pRe);
402 }
403 }else{
404 if( zOld && zNew ){
405 if( fossil_strcmp(zOld, zNew)!=0 ){
406 @ Modified %z(href("%R/finfo?name=%T&m=%!S",zName,zNew))%h(zName)</a>
@@ -427,18 +462,172 @@
427 }else{
428 @ Added %z(href("%R/finfo?name=%T&m=%!S",zName,zNew))%h(zName)</a>
429 @ version %z(href("%R/artifact/%!S",zNew))[%S(zNew)]</a>.
430 }
431 if( diffFlags ){
432 append_diff(zOld, zNew, diffFlags, pRe);
433 }else if( zOld && zNew && fossil_strcmp(zOld,zNew)!=0 ){
434 @ &nbsp;&nbsp;
435 @ %z(href("%R/fdiff?v1=%!S&v2=%!S",zOld,zNew))[diff]</a>
436 }
437 }
438 @ </p>
439 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
440
441 /*
442 ** Generate javascript to enhance HTML diffs.
443 */
444 void append_diff_javascript(int sideBySide){
@@ -596,19 +785,24 @@
596 }
597
598 /*
599 ** WEBPAGE: vinfo
600 ** WEBPAGE: ci
 
601 ** URL: /ci/ARTIFACTID
602 ** OR: /ci?name=ARTIFACTID
603 **
604 ** Display information about a particular check-in. The exact
605 ** same information is shown on the /info page if the name query
606 ** parameter to /info describes a check-in.
607 **
608 ** The ARTIFACTID can be a unique prefix for the HASH of the check-in,
609 ** or a tag or branch name that identifies the check-in.
 
 
 
 
610 */
611 void ci_page(void){
612 Stmt q1, q2, q3;
613 int rid;
614 int isLeaf;
@@ -621,14 +815,37 @@
621 ReCompiled *pRe = 0; /* regex */
622 const char *zW; /* URL param for ignoring whitespace */
623 const char *zPage = "vinfo"; /* Page that shows diffs */
624 const char *zPageHide = "ci"; /* Page that hides diffs */
625 const char *zBrName; /* Branch name */
 
 
626
627 login_check_credentials();
628 if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
629 zName = P("name");
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
630 rid = name_to_rid_www("name");
631 if( rid==0 ){
632 style_header("Check-in Information Error");
633 @ No such object: %h(g.argv[2])
634 style_footer();
@@ -667,11 +884,15 @@
667 int okWiki = 0;
668 Blob wiki_read_links = BLOB_INITIALIZER;
669 Blob wiki_add_links = BLOB_INITIALIZER;
670
671 Th_Store("current_checkin", zName);
672 style_header("Check-in [%S]", zUuid);
 
 
 
 
673 login_anonymous_available();
674 zEUser = db_text(0,
675 "SELECT value FROM tagxref"
676 " WHERE tagid=%d AND rid=%d AND tagtype>0",
677 TAG_USER, rid);
@@ -873,14 +1094,25 @@
873 wiki_render_associated("checkin", zUuid, 0);
874 }
875 render_backlink_graph(zUuid, "<div class=\"section\">References</div>\n");
876 @ <div class="section">Context</div>
877 render_checkin_context(rid, 0, 0);
878 @ <div class="section">Changes</div>
 
 
 
 
879 @ <div class="sectionmenu">
880 diffFlags = construct_diff_flags(diffType);
881 zW = (diffFlags&DIFF_IGNORE_ALLWS)?"&w":"";
 
 
 
 
 
 
 
882 if( diffType!=0 ){
883 @ %z(chref("button","%R/%s/%T?diff=0",zPageHide,zName))\
884 @ Hide&nbsp;Diffs</a>
885 }
886 if( diffType!=1 ){
@@ -898,48 +1130,163 @@
898 }else{
899 @ %z(chref("button","%R/%s/%T?diff=%d&w",zPage,zName,diffType))
900 @ Ignore&nbsp;Whitespace</a>
901 }
902 }
903 if( zParent ){
904 @ %z(chref("button","%R/vpatch?from=%!S&to=%!S",zParent,zUuid))
905 @ Patch</a>
 
 
 
 
906 }
907 if( g.perm.Admin ){
908 @ %z(chref("button","%R/mlink?ci=%!S",zUuid))MLink Table</a>
909 }
910 @</div>
911 if( pRe ){
912 @ <p><b>Only differences that match regular expression "%h(zRe)"
913 @ are shown.</b></p>
914 }
915 db_prepare(&q3,
916 "SELECT name,"
917 " mperm,"
918 " (SELECT uuid FROM blob WHERE rid=mlink.pid),"
919 " (SELECT uuid FROM blob WHERE rid=mlink.fid),"
920 " (SELECT name FROM filename WHERE filename.fnid=mlink.pfnid)"
921 " FROM mlink JOIN filename ON filename.fnid=mlink.fnid"
922 " WHERE mlink.mid=%d AND NOT mlink.isaux"
923 " AND (mlink.fid>0"
924 " OR mlink.fnid NOT IN (SELECT pfnid FROM mlink WHERE mid=%d))"
925 " ORDER BY name /*sort*/",
926 rid, rid
927 );
928 while( db_step(&q3)==SQLITE_ROW ){
929 const char *zName = db_column_text(&q3,0);
930 int mperm = db_column_int(&q3, 1);
931 const char *zOld = db_column_text(&q3,2);
932 const char *zNew = db_column_text(&q3,3);
933 const char *zOldName = db_column_text(&q3, 4);
934 append_file_change_line(zName, zOld, zNew, zOldName, diffFlags,pRe,mperm);
935 }
936 db_finalize(&q3);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
937 append_diff_javascript(diffType==2);
938 cookie_render();
939 style_footer();
940 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
941
942 /*
943 ** WEBPAGE: winfo
944 ** URL: /winfo?name=UUID
945 **
@@ -1216,11 +1563,11 @@
1216 style_submenu_element("Side-by-Side Diff",
1217 "%R/vdiff?%s&diff=2%s%T%s",
1218 zQuery,
1219 zGlob ? "&glob=" : "", zGlob ? zGlob : "", zW);
1220 }
1221 if( diffType!=1 ) {
1222 style_submenu_element("Unified Diff",
1223 "%R/vdiff?%s&diff=1%s%T%s",
1224 zQuery,
1225 zGlob ? "&glob=" : "", zGlob ? zGlob : "", zW);
1226 }
@@ -1400,11 +1747,11 @@
1400 }
1401 cnt++;
1402 continue;
1403 }
1404 if( !sameFilename ){
1405 if( prevName && showDetail ) {
1406 @ </ul>
1407 }
1408 if( mPerm==PERM_LNK ){
1409 @ <li>Symbolic link
1410 objType |= OBJTYPE_SYMLINK;
@@ -1520,11 +1867,11 @@
1520 objType |= OBJTYPE_TICKET;
1521 }else if( zType[0]=='c' ){
1522 @ Manifest of check-in
1523 objType |= OBJTYPE_CHECKIN;
1524 }else if( zType[0]=='e' ){
1525 if( eventTagId != 0) {
1526 @ Instance of technote
1527 objType |= OBJTYPE_EVENT;
1528 hyperlink_to_event_tagid(db_column_int(&q, 5));
1529 }else{
1530 @ Attachment to technote
@@ -1567,11 +1914,11 @@
1567 }else{
1568 @ Attachment "%h(zFilename)" to
1569 }
1570 objType |= OBJTYPE_ATTACHMENT;
1571 if( fossil_is_uuid(zTarget) ){
1572 if ( db_exists("SELECT 1 FROM tag WHERE tagname='tkt-%q'",
1573 zTarget)
1574 ){
1575 if( g.perm.Hyperlink && g.anon.RdTkt ){
1576 @ ticket [%z(href("%R/tktview?name=%!S",zTarget))%S(zTarget)</a>]
1577 }else{
@@ -1625,11 +1972,13 @@
1625 }
1626
1627
1628 /*
1629 ** WEBPAGE: fdiff
 
1630 ** URL: fdiff?v1=UUID&v2=UUID
 
1631 **
1632 ** Two arguments, v1 and v2, identify the artifacts to be diffed.
1633 ** Show diff side by side unless sbs is 0. Generate plain text if
1634 ** "patch" is present, otherwise generate "pretty" HTML.
1635 **
@@ -1637,10 +1986,14 @@
1637 **
1638 ** If the "from" and "to" query parameters are both present, then they are
1639 ** the names of two files within the check-in "ci" that are diffed. If the
1640 ** "ci" parameter is omitted, then the most recent check-in ("tip") is
1641 ** used.
 
 
 
 
1642 **
1643 ** Additional parameters:
1644 **
1645 ** dc=N Show N lines of context around each diff
1646 ** patch Use the patch diff format
@@ -1658,17 +2011,23 @@
1658 const char *zRe;
1659 ReCompiled *pRe = 0;
1660 u64 diffFlags;
1661 u32 objdescFlags = 0;
1662 int verbose = PB("verbose");
 
 
1663
1664 login_check_credentials();
1665 if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
1666 cookie_link_parameter("diff","diff","2");
1667 diffType = atoi(PD("diff","2"));
1668 cookie_render();
1669 if( P("from") && P("to") ){
 
 
 
 
1670 v1 = artifact_from_ci_and_filename("from");
1671 v2 = artifact_from_ci_and_filename("to");
1672 }else{
1673 Stmt q;
1674 v1 = name_to_rid_www("v1");
@@ -1707,14 +2066,19 @@
1707 if( zRe ) re_compile(&pRe, zRe, 0);
1708 if( verbose ) objdescFlags |= OBJDESC_DETAIL;
1709 if( isPatch ){
1710 Blob c1, c2, *pOut;
1711 pOut = cgi_output_blob();
 
1712 cgi_set_content_type("text/plain");
1713 diffFlags = 4;
1714 content_get(v1, &c1);
1715 content_get(v2, &c2);
 
 
 
 
1716 text_diff(&c1, &c2, pOut, pRe, diffFlags);
1717 blob_reset(&c1);
1718 blob_reset(&c2);
1719 return;
1720 }
@@ -1723,38 +2087,62 @@
1723 zV2 = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", v2);
1724 diffFlags = construct_diff_flags(diffType) | DIFF_HTML;
1725
1726 style_header("Diff");
1727 style_submenu_checkbox("w", "Ignore Whitespace", 0, 0);
1728 if( diffType==2 ){
1729 style_submenu_element("Unified Diff", "%R/fdiff?v1=%T&v2=%T&diff=1",
1730 P("v1"), P("v2"));
1731 }else{
1732 style_submenu_element("Side-by-side Diff", "%R/fdiff?v1=%T&v2=%T&diff=2",
1733 P("v1"), P("v2"));
 
 
 
 
 
 
 
 
 
 
1734 }
1735 style_submenu_checkbox("verbose", "Verbose", 0, 0);
1736 style_submenu_element("Patch", "%R/fdiff?v1=%T&v2=%T&patch",
1737 P("v1"), P("v2"));
 
 
 
 
1738
1739 if( P("smhdr")!=0 ){
1740 @ <h2>Differences From Artifact
1741 @ %z(href("%R/artifact/%!S",zV1))[%S(zV1)]</a> To
1742 @ %z(href("%R/artifact/%!S",zV2))[%S(zV2)]</a>.</h2>
 
 
 
 
 
1743 }else{
1744 @ <h2>Differences From
1745 @ Artifact %z(href("%R/artifact/%!S",zV1))[%S(zV1)]</a>:</h2>
1746 object_description(v1, objdescFlags,0, 0);
1747 @ <h2>To Artifact %z(href("%R/artifact/%!S",zV2))[%S(zV2)]</a>:</h2>
1748 object_description(v2, objdescFlags,0, 0);
 
 
 
 
 
1749 }
1750 if( pRe ){
1751 @ <b>Only differences that match regular expression "%h(zRe)"
1752 @ are shown.</b>
1753 }
1754 @ <hr />
1755 append_diff(zV1, zV2, diffFlags, pRe);
1756 append_diff_javascript(diffType);
1757 style_footer();
1758 }
1759
1760 /*
@@ -1908,13 +2296,16 @@
1908 }
1909
1910 /*
1911 ** WEBPAGE: hexdump
1912 ** URL: /hexdump?name=ARTIFACTID
 
1913 **
1914 ** Show the complete content of a file identified by ARTIFACTID
1915 ** as preformatted text.
 
 
1916 **
1917 ** Other parameters:
1918 **
1919 ** verbose Show more detail when describing the object
1920 */
@@ -1922,42 +2313,61 @@
1922 int rid;
1923 Blob content;
1924 Blob downloadName;
1925 char *zUuid;
1926 u32 objdescFlags = 0;
 
 
1927
1928 rid = name_to_rid_www("name");
1929 login_check_credentials();
1930 if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
1931 if( rid==0 ) fossil_redirect_home();
1932 if( g.perm.Admin ){
1933 const char *zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", rid);
1934 if( db_exists("SELECT 1 FROM shun WHERE uuid=%Q", zUuid) ){
1935 style_submenu_element("Unshun", "%s/shun?accept=%s&sub=1#delshun",
1936 g.zTop, zUuid);
1937 }else{
1938 style_submenu_element("Shun", "%s/shun?shun=%s#addshun", g.zTop, zUuid);
 
 
1939 }
1940 }
1941 style_header("Hex Artifact Content");
1942 zUuid = db_text("?","SELECT uuid FROM blob WHERE rid=%d", rid);
1943 @ <h2>Artifact
1944 style_copy_button(1, "hash-ar", 0, 2, "%s", zUuid);
1945 if( g.perm.Setup ){
1946 @ (%d(rid)):</h2>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1947 }else{
1948 @ :</h2>
 
1949 }
1950 blob_zero(&downloadName);
1951 if( P("verbose")!=0 ) objdescFlags |= OBJDESC_DETAIL;
1952 object_description(rid, objdescFlags, 0, &downloadName);
1953 style_submenu_element("Download", "%R/raw/%s?at=%T",
1954 zUuid, file_tail(blob_str(&downloadName)));
1955 @ <hr />
1956 content_get(rid, &content);
 
 
 
 
1957 @ <blockquote><pre>
1958 hexdump(&content);
 
1959 @ </pre></blockquote>
1960 style_footer();
1961 }
1962
1963 /*
@@ -2131,10 +2541,13 @@
2131 ** if name= cannot be understood as a hash, a default "tip" value is
2132 ** used for ci=.
2133 **
2134 ** For /file, name= can only be interpreted as a filename. As before,
2135 ** a default value of "tip" is used for ci= if ci= is omitted.
 
 
 
2136 */
2137 void artifact_page(void){
2138 int rid = 0;
2139 Blob content;
2140 const char *zMime;
@@ -2153,10 +2566,11 @@
2153 HQuery url;
2154 char *zCIUuid = 0;
2155 int isSymbolicCI = 0; /* ci= exists and is a symbolic name, not a hash */
2156 int isBranchCI = 0; /* ci= refers to a branch name */
2157 char *zHeader = 0;
 
2158
2159 login_check_credentials();
2160 if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
2161
2162 /* Capture and normalize the name= and ci= query parameters */
@@ -2200,11 +2614,14 @@
2200 ** name= as a hash for /artifact and /whatis. But for not for /file.
2201 ** For /file, a name= without a ci= while prefer to use the default
2202 ** "tip" value for ci=. */
2203 rid = name_to_rid(zName);
2204 }
2205 if( rid==0 ){
 
 
 
2206 rid = artifact_from_ci_and_filename(0);
2207 }
2208
2209 if( rid==0 ){ /* Artifact not found */
2210 if( isFile ){
@@ -2237,11 +2654,25 @@
2237 }
2238 zUuid = db_text("?", "SELECT uuid FROM blob WHERE rid=%d", rid);
2239
2240 asText = P("txt")!=0;
2241 if( isFile ){
2242 if( zCI==0 || fossil_strcmp(zCI,"tip")==0 ){
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2243 zCI = "tip";
2244 isSymbolicCI = 1; /* Mark default-to-"tip" as symbolic */
2245 @ <h2>File %z(href("%R/finfo?name=%T&m=tip",zName))%h(zName)</a>
2246 @ from the %z(href("%R/info/tip"))latest check-in</a></h2>
2247 }else{
@@ -2258,15 +2689,17 @@
2258 }else{
2259 @ as of check-in [%z(href("%R/info/%!S",zCIUuid))%S(zCIUuid)</a>]</h2>
2260 }
2261 blob_reset(&path);
2262 }
2263 style_submenu_element("Artifact", "%R/artifact/%S", zUuid);
2264 style_submenu_element("Annotate", "%R/annotate?filename=%T&checkin=%T",
2265 zName, zCI);
2266 style_submenu_element("Blame", "%R/blame?filename=%T&checkin=%T",
2267 zName, zCI);
 
 
2268 blob_init(&downloadName, zName, -1);
2269 objType = OBJTYPE_CONTENT;
2270 }else{
2271 @ <h2>Artifact
2272 style_copy_button(1, "hash-ar", 0, 2, "%s", zUuid);
@@ -2278,29 +2711,33 @@
2278 blob_zero(&downloadName);
2279 if( asText ) objdescFlags &= ~OBJDESC_BASE;
2280 objType = object_description(rid, objdescFlags,
2281 (isFile?zName:0), &downloadName);
2282 }
2283 if( !descOnly && P("download")!=0 ){
2284 cgi_redirectf("%R/raw/%s?at=%T",
2285 db_text("x", "SELECT uuid FROM blob WHERE rid=%d", rid),
2286 file_tail(blob_str(&downloadName)));
2287 /*NOTREACHED*/
2288 }
2289 if( g.perm.Admin ){
2290 const char *zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", rid);
2291 if( db_exists("SELECT 1 FROM shun WHERE uuid=%Q", zUuid) ){
2292 style_submenu_element("Unshun", "%s/shun?accept=%s&sub=1#accshun",
2293 g.zTop, zUuid);
2294 }else{
2295 style_submenu_element("Shun", "%s/shun?shun=%s#addshun", g.zTop, zUuid);
 
 
2296 }
2297 }
2298
2299 if( isFile ){
2300 if( isSymbolicCI ){
2301 zHeader = mprintf("%s at %s", file_tail(zName), zCI);
 
 
2302 }else if( zCI ){
2303 zHeader = mprintf("%s at [%S]", file_tail(zName), zCIUuid);
2304 }else{
2305 zHeader = mprintf("%s", file_tail(zName));
2306 }
@@ -2326,13 +2763,17 @@
2326 const char *zIp = db_column_text(&q,2);
2327 @ <p>Received on %s(zDate) from %h(zUser) at %h(zIp).</p>
2328 }
2329 db_finalize(&q);
2330 }
2331 style_submenu_element("Download", "%R/raw/%s?at=%T", zUuid, file_tail(zName));
2332 if( db_exists("SELECT 1 FROM mlink WHERE fid=%d", rid) ){
2333 style_submenu_element("Check-ins Using", "%R/timeline?n=200&uf=%s", zUuid);
 
 
 
 
2334 }
2335 zMime = mimetype_from_name(blob_str(&downloadName));
2336 if( zMime ){
2337 if( fossil_strcmp(zMime, "text/html")==0 ){
2338 if( asText ){
@@ -2348,24 +2789,38 @@
2348 }else{
2349 renderAsWiki = 1;
2350 style_submenu_element("Text", "%s", url_render(&url, "txt", "1", 0, 0));
2351 }
2352 }
2353 if( fileedit_is_editable(zName) ){
2354 style_submenu_element("Edit",
2355 "%R/fileedit?filename=%T&checkin=%!S",
2356 zName, zCI);
 
 
2357 }
2358 }
2359 if( (objType & (OBJTYPE_WIKI|OBJTYPE_TICKET))!=0 ){
2360 style_submenu_element("Parsed", "%R/info/%s", zUuid);
2361 }
2362 if( descOnly ){
2363 style_submenu_element("Content", "%R/artifact/%s", zUuid);
2364 }else{
2365 @ <hr />
2366 content_get(rid, &content);
 
 
 
 
 
 
 
 
 
 
 
 
2367 if( renderAsWiki ){
2368 wiki_render_by_mimetype(&content, zMime);
2369 }else if( renderAsHtml ){
2370 @ <iframe src="%R/raw/%s(zUuid)"
2371 @ width="100%%" frameborder="0" marginwidth="0" marginheight="0"
@@ -2377,11 +2832,15 @@
2377 @ this.height=this.contentDocument.documentElement.scrollHeight + 75;
2378 @ }
2379 @ );
2380 @ </script>
2381 }else{
2382 style_submenu_element("Hex", "%s/hexdump?name=%s", g.zTop, zUuid);
 
 
 
 
2383 if( zLn==0 || atoi(zLn)==0 ){
2384 style_submenu_checkbox("ln", "Line Numbers", 0, 0);
2385 }
2386 blob_to_utf8_no_bom(&content, 0);
2387 zMime = mimetype_from_content(&content);
2388
--- src/info.c
+++ src/info.c
@@ -1,5 +1,19 @@
1 /*TODO
2 ** o Should /file behave differently for non-existent local files?
3 ** o Look at adding an "extras" option (non-added, non-ignored files).
4 ** o Look at sifting out "one line" differences from those with "diff blocks".
5 ** Perhaps reset the query and re-run, displaying only non-diff entries the
6 ** first time? Or perhaps buffer the output (probably bad idea).
7 ** o If I keep the extra <hr/> I've added after a diff-block, is there a way
8 ** to avoid the double <hr/> if the last entry has a diff-block?
9 ** o Find a place to add links to /local.
10 ** o Remove //TODO TESTING HACK TODO
11 ** ?? In hexdump_page(), should content (and downloadName?) be reset/freed?
12 ** ?? In the test fossil (\x\$Test\Fossil) there are (at time of writing) two
13 ** commits under the same artifact... is this normal?
14 */
15 /*
16 ** Copyright (c) 2007 D. Richard Hipp
17 **
18 ** This program is free software; you can redistribute it and/or
19 ** modify it under the terms of the Simplified BSD License (also
@@ -322,17 +336,36 @@
336 |TIMELINE_CHPICK,
337 0, 0, 0, rid, rid2, 0);
338 db_finalize(&q);
339 }
340
341 /*
342 ** Read the content of file zName (prepended with the checkout directory)
343 ** and put it into the uninitialized blob. The blob is zeroed if the file
344 ** does not exist (if the file cannot be read, blob_read_from_file() aborts
345 ** the program).
346 */
347 static void content_from_file(
348 const char *zName, /* Filename (relative to checkout) of file to be read */
349 Blob *pBlob /* Pointer to blob to receive contents */
350 ){
351 const char *zFullPath = mprintf("%s%s", g.zLocalRoot, zName);
352 blob_zero(pBlob);
353 if( file_size(zFullPath, ExtFILE)>=0 ){
354 blob_read_from_file(pBlob, zFullPath, ExtFILE);
355 }
356 }
357
358 /*
359 ** Append the difference between artifacts to the output
360 ** If zLocal is not NULL, instead compare against the local
361 ** copy of the file it names in the repository.
362 */
363 static void append_diff(
364 const char *zFrom, /* Diff from this artifact */
365 const char *zTo, /* ... to this artifact */
366 const char *zLocal, /* ... OR to this local file */
367 u64 diffFlags, /* Diff formatting flags */
368 ReCompiled *pRe /* Only show change matching this regex */
369 ){
370 int fromid;
371 int toid;
@@ -344,10 +377,12 @@
377 blob_zero(&from);
378 }
379 if( zTo ){
380 toid = uuid_to_rid(zTo, 0);
381 content_get(toid, &to);
382 }else if( zLocal ){ /* Read the file on disk */
383 content_from_file(zLocal, &to);
384 }else{
385 blob_zero(&to);
386 }
387 blob_zero(&out);
388 if( diffFlags & DIFF_SIDEBYSIDE ){
@@ -396,11 +431,11 @@
431 }
432 }else{
433 @ Changes to %h(zName).
434 }
435 if( diffFlags ){
436 append_diff(zOld, zNew, NULL, diffFlags, pRe);
437 }
438 }else{
439 if( zOld && zNew ){
440 if( fossil_strcmp(zOld, zNew)!=0 ){
441 @ Modified %z(href("%R/finfo?name=%T&m=%!S",zName,zNew))%h(zName)</a>
@@ -427,18 +462,172 @@
462 }else{
463 @ Added %z(href("%R/finfo?name=%T&m=%!S",zName,zNew))%h(zName)</a>
464 @ version %z(href("%R/artifact/%!S",zNew))[%S(zNew)]</a>.
465 }
466 if( diffFlags ){
467 append_diff(zOld, zNew, NULL, diffFlags, pRe);
468 }else if( zOld && zNew && fossil_strcmp(zOld,zNew)!=0 ){
469 @ &nbsp;&nbsp;
470 @ %z(href("%R/fdiff?v1=%!S&v2=%!S",zOld,zNew))[diff]</a>
471 }
472 }
473 @ </p>
474 }
475
476 /*
477 ** Append notice of executable or symlink being gained or lost.
478 */
479 static void append_status(
480 const char *zAction, /* Whether status was gained or lost */
481 const char *zStatus, /* The status that was gained/lost */
482 const char *zName, /* Name of file */
483 const char *zOld /* Existing artifact */
484 ){
485 if( !g.perm.Hyperlink ){
486 @ %h(zName) %h(zAction) %h(zStatus) status.
487 }else{
488 @ %z(href("%R/finfo?name=%T&m=%!S",zName,zOld))%h(zName)</a>
489 @ from %z(href("%R/artifact/%!S",zOld))[%S(zOld)]</a>
490 @ %h(zAction) %h(zStatus) status.
491 }
492 }
493
494 /*
495 ** Append web-page output that shows changes between a file at last check-in
496 ** and its current state on disk (i.e. any uncommitted changes). The status
497 ** ("changed", "missing" etc.) is a blend of that from append_file_change_line()
498 ** and diff_against_disk() (in "diffcmd.c").
499 **
500 ** The file-differences (if being shown) use append_diff() as before, but
501 ** there is an additional parameter (zLocal) which, if non-NULL, causes it
502 ** to compare the checked-in version against the named file on disk.
503 */
504 static void append_local_file_change_line(
505 const char *zName, /* Name of the file that has changed */
506 const char *zOld, /* blob.uuid before change. NULL for added files */
507 int isDeleted, /* Has the file-on-disk been removed from Fossil? */
508 int isChnged, /* Has the file changed in some way (see vfile.c) */
509 int isNew, /* Has the file been ADDed (but not yet committed? */
510 int isLink, /* Is the file a symbolic link? */
511 u64 diffFlags, /* Flags for text_diff(). Zero to omit diffs */
512 ReCompiled *pRe /* Only show diffs that match this regex, if not NULL */
513 ){
514 char *zFullName = mprintf("%s%s", g.zLocalRoot, zName);
515 int isFilePresent = !file_access(zFullName, F_OK);
516 int showDiff = 0;
517 //TODO TESTING HACK TODO
518 if( strncmp(zName,"aa",2)==0 ){
519 isChnged = atoi(zName+2);
520 }
521 //TODO TESTING HACK TODO
522 @ <p>
523 if( !g.perm.Hyperlink ){
524 if( isDeleted ){
525 if( isFilePresent ){
526 @ Deleted %h(zName) (still present as a local file).
527 showDiff = 1;
528 }else{
529 @ Deleted %h(zName).
530 }
531 }else if( isNew ){
532 if( isFilePresent ){
533 @ Added %h(zName) but not committed.
534 }else{
535 @ Missing %h(zName) (was added to checkout).
536 }
537 }else switch( isChnged ){
538 /*TODO
539 ** These "special cases" have not been properly tested (by creating
540 ** entries in a in a repository to trigger them), but they do display
541 ** as expected when "forced" to appear.
542 */
543 case 3:
544 @ Added %h(zName) due to a merge.
545 break;
546 case 5:
547 @ Added %h(zName) due to an integration merge.
548 break;
549 case 6: append_status( "gained", "executable", zName, zOld); break;
550 case 7: append_status( "gained", "symlink", zName, zOld); break;
551 case 8: append_status( "lost", "executable", zName, zOld); break;
552 case 9: append_status( "lost", "symlink", zName, zOld); break;
553
554 default: /* Normal edit */
555 @ Local changes of %h(zName).
556 showDiff = 1;
557 }
558 if( showDiff && diffFlags ){
559 append_diff(zOld, NULL, zName, diffFlags, pRe);
560 @ <hr/>
561 }
562 }else{
563 if( isDeleted ){
564 if( isFilePresent ){ /* DELETEd but still on disk */
565 @ Deleted %z(href("%R/finfo?name=%T&m=%!S",zName,zOld))%h(zName)</a>
566 @ from %z(href("%R/artifact/%!S",zOld))[%S(zOld)]</a> (still present
567 @ as %z(href("%R/file/%T?ci=ckout&annot=removed from checkout",zName))
568 @ [local file]</a>).
569 showDiff = 1;
570 }else{ /* DELETEd and deleted from disk */
571 @ Deleted %z(href("%R/finfo?name=%T&m=%!S",zName,zOld))%h(zName)</a>
572 @ from %z(href("%R/artifact/%!S",zOld))[%S(zOld)]</a>.
573 }
574 }else if( isNew ){
575 if( isFilePresent ){ /* ADDed and present on disk */
576 @ Added %z(href("%R/file/%T?ci=ckout",zName))%h(zName)</a>
577 @ but not committed.
578 }else{ /* ADDed but not present on disk */
579 @ Missing %h(zName) (was added to checkout).
580 }
581 }else switch( isChnged ){
582 /*TODO Not fully tested... see see no-hyperlink version above */
583 case 3: /* Added by a merge */
584 @ Added
585 @ %z(href("%R/file/%T?ci=ckout&annot=added by merge",zName))%h(zName)
586 @ </a> to %z(href("%R/artifact/%!S",zOld))[%S(zOld)]</a> due to merge.
587 break;
588 case 5: /* Added by an integration merge */
589 @ Added
590 @ %z(href("%R/file/%T?ci=ckout&annot=added by integration merge",zName))
591 @ %h(zName)</a> to
592 @ %z(href("%R/artifact/%!S",zOld))[%S(zOld)]</a> due to integrate merge.
593 break;
594 case 6: append_status( "gained", "executable", zName, zOld); break;
595 case 7: append_status( "gained", "symlink", zName, zOld); break;
596 case 8: append_status( "lost", "executable", zName, zOld); break;
597 case 9: append_status( "lost", "symlink", zName, zOld); break;
598
599 default: /* Normal edit */
600 @ Local changes of
601 @ %z(href("%R/finfo?name=%T&m=%!S",zName,zOld))%h(zName)</a>
602 @ from %z(href("%R/artifact/%!S",zOld))[%S(zOld)]</a> to
603 @ %z(href("%R/file/%T?ci=ckout&annot=edited locally",zName))
604 @ [local file]</a>
605 showDiff = 1;
606 }
607 if( showDiff ){
608 if( diffFlags ){
609 append_diff(zOld, NULL, zName, diffFlags, pRe);
610 /*TODO
611 ** Not related to the local-mode, but if two or more files have been
612 ** changed in a commit/"local changes", it is sometimes easy to miss
613 ** the switch from one to the other. The following (IMHO) makes things
614 ** clearer, but can mean there's a double rule at the bottom of the
615 ** page. If kept, a similar <hr/> should probably be added to
616 ** append_file_change_line() (but would need to check how things look
617 ** when called from /vinfo).
618 */
619 @ <hr/>
620 }else if( isChnged ){
621 @ &nbsp;&nbsp;
622 @ %z(href("%R/localdiff?name=%T",zName))[diff]</a>
623 }
624 }
625 }
626 @ </p>
627 fossil_free(zFullName);
628 }
629
630 /*
631 ** Generate javascript to enhance HTML diffs.
632 */
633 void append_diff_javascript(int sideBySide){
@@ -596,19 +785,24 @@
785 }
786
787 /*
788 ** WEBPAGE: vinfo
789 ** WEBPAGE: ci
790 ** WEBPAGE: local
791 ** URL: /ci/ARTIFACTID
792 ** OR: /ci?name=ARTIFACTID
793 **
794 ** Display information about a particular check-in. The exact
795 ** same information is shown on the /info page if the name query
796 ** parameter to /info describes a check-in.
797 **
798 ** The ARTIFACTID can be a unique prefix for the HASH of the check-in,
799 ** or a tag or branch name that identifies the check-in.
800 **
801 ** Use of /local (or the use of "ckout" for ARTIFACTID) will show the
802 ** same header details as /ci/tip, but then displays any (uncommitted)
803 ** edits made to files in the checkout directory.
804 */
805 void ci_page(void){
806 Stmt q1, q2, q3;
807 int rid;
808 int isLeaf;
@@ -621,14 +815,37 @@
815 ReCompiled *pRe = 0; /* regex */
816 const char *zW; /* URL param for ignoring whitespace */
817 const char *zPage = "vinfo"; /* Page that shows diffs */
818 const char *zPageHide = "ci"; /* Page that hides diffs */
819 const char *zBrName; /* Branch name */
820 int bLocalMode; /* TRUE for /local; FALSE otherwise */
821 int vid; /* Virtual file system? */
822
823 login_check_credentials();
824 if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
825 zName = P("name");
826 /* Local mode is selected by either "/local" or with a "name" of "ckout".
827 ** First, check we have access to the checkout (and report to the user if we
828 ** don't), then refresh the "vfile" table (recording which files in the
829 ** checkout have changed etc.). We then change the "name" parameter to "tip"
830 ** so that the "header" section displays info about the check-in that the
831 ** checkout came from.
832 */
833 bLocalMode = (g.zPath[0]=='l') || (fossil_strcmp(zName,"ckout")==0);
834 if( bLocalMode ){
835 vid = g.localOpen ? db_lget_int("checkout", 0) : 0;
836 if( vid==0 ){
837 /*TODO Is this the right response? */
838 style_header("No Local Checkout");
839 @ No access to local checkout.
840 style_footer();
841 return;
842 }
843 vfile_check_signature(vid, CKSIG_ENOTFILE);
844 zName = "tip";
845 cgi_replace_parameter("name","tip"); /* Needed to get rid below */
846 }
847 rid = name_to_rid_www("name");
848 if( rid==0 ){
849 style_header("Check-in Information Error");
850 @ No such object: %h(g.argv[2])
851 style_footer();
@@ -667,11 +884,15 @@
884 int okWiki = 0;
885 Blob wiki_read_links = BLOB_INITIALIZER;
886 Blob wiki_add_links = BLOB_INITIALIZER;
887
888 Th_Store("current_checkin", zName);
889 if( bLocalMode ){
890 style_header("Local Changes from Check-in [%S]", zUuid);
891 }else{
892 style_header("Check-in [%S]", zUuid);
893 }
894 login_anonymous_available();
895 zEUser = db_text(0,
896 "SELECT value FROM tagxref"
897 " WHERE tagid=%d AND rid=%d AND tagtype>0",
898 TAG_USER, rid);
@@ -873,14 +1094,25 @@
1094 wiki_render_associated("checkin", zUuid, 0);
1095 }
1096 render_backlink_graph(zUuid, "<div class=\"section\">References</div>\n");
1097 @ <div class="section">Context</div>
1098 render_checkin_context(rid, 0, 0);
1099 if( bLocalMode ){
1100 @ <div class="section">Uncommitted Changes</div>
1101 }else{
1102 @ <div class="section">Changes</div>
1103 }
1104 @ <div class="sectionmenu">
1105 diffFlags = construct_diff_flags(diffType);
1106 zW = (diffFlags&DIFF_IGNORE_ALLWS)?"&w":"";
1107 /* In local mode, having displayed the header info for "tip", switch zName
1108 ** to be "ckout" so the style-altering links (unified or side-by-side etc.)
1109 ** will correctly re-select local-mode.
1110 */
1111 if( bLocalMode ){
1112 zName = "ckout";
1113 }
1114 if( diffType!=0 ){
1115 @ %z(chref("button","%R/%s/%T?diff=0",zPageHide,zName))\
1116 @ Hide&nbsp;Diffs</a>
1117 }
1118 if( diffType!=1 ){
@@ -898,48 +1130,163 @@
1130 }else{
1131 @ %z(chref("button","%R/%s/%T?diff=%d&w",zPage,zName,diffType))
1132 @ Ignore&nbsp;Whitespace</a>
1133 }
1134 }
1135 if( bLocalMode ){
1136 @ %z(chref("button","%R/localpatch")) Patch</a>
1137 }else{
1138 if( zParent ){
1139 @ %z(chref("button","%R/vpatch?from=%!S&to=%!S",zParent,zUuid))
1140 @ Patch</a>
1141 }
1142 }
1143 if( g.perm.Admin ){
1144 @ %z(chref("button","%R/mlink?ci=%!S",zUuid))MLink Table</a>
1145 }
1146 @</div>
1147 if( pRe ){
1148 @ <p><b>Only differences that match regular expression "%h(zRe)"
1149 @ are shown.</b></p>
1150 }
1151 if( bLocalMode ){
1152 /* Following SQL taken from diff_against_disk() in diffcmd.c */
1153 db_begin_transaction();
1154 db_prepare(&q3,
1155 "SELECT pathname, deleted, chnged , rid==0, rid, islink"
1156 " FROM vfile"
1157 " WHERE vid=%d"
1158 " AND (deleted OR chnged OR rid==0)"
1159 " ORDER BY pathname /*scan*/",
1160 vid
1161 );
1162 /* TODO Have the option of showing "extras" (non-ignored files in the
1163 ** checkout directory that have not been ADDed). If done, they should
1164 ** be ahead of any potential "diff-blocks" so they don't get lost
1165 ** (which is the inspiration for...)
1166 ** TODO Consider making this two-pass, where the first pass skips anything
1167 ** that would show a diff-block (and the second pass only shows such
1168 ** entries). This would group all "one-line" entries at the top so
1169 ** they are less likely to be missed.
1170 ** TODO Possibly (at some stage) have an option to commit?
1171 */
1172 while( db_step(&q3)==SQLITE_ROW ){
1173 const char *zPathname = db_column_text(&q3,0);
1174 int isDeleted = db_column_int(&q3, 1);
1175 int isChnged = db_column_int(&q3,2);
1176 int isNew = db_column_int(&q3,3);
1177 int srcid = db_column_int(&q3, 4);
1178 int isLink = db_column_int(&q3, 5);
1179 char *zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", srcid);
1180 append_local_file_change_line(zPathname, zUuid,
1181 isDeleted, isChnged, isNew, isLink, diffFlags,pRe);
1182 free(zUuid);
1183 }
1184 db_finalize(&q3);
1185 db_end_transaction(1); /* ROLLBACK */
1186 }else{ /* Normal, non-local-mode: show diffs against parent */
1187 db_prepare(&q3,
1188 "SELECT name,"
1189 " mperm,"
1190 " (SELECT uuid FROM blob WHERE rid=mlink.pid),"
1191 " (SELECT uuid FROM blob WHERE rid=mlink.fid),"
1192 " (SELECT name FROM filename WHERE filename.fnid=mlink.pfnid)"
1193 " FROM mlink JOIN filename ON filename.fnid=mlink.fnid"
1194 " WHERE mlink.mid=%d AND NOT mlink.isaux"
1195 " AND (mlink.fid>0"
1196 " OR mlink.fnid NOT IN (SELECT pfnid FROM mlink WHERE mid=%d))"
1197 " ORDER BY name /*sort*/",
1198 rid, rid
1199 );
1200 while( db_step(&q3)==SQLITE_ROW ){
1201 const char *zName = db_column_text(&q3,0);
1202 int mperm = db_column_int(&q3, 1);
1203 const char *zOld = db_column_text(&q3,2);
1204 const char *zNew = db_column_text(&q3,3);
1205 const char *zOldName = db_column_text(&q3, 4);
1206 append_file_change_line(zName, zOld, zNew, zOldName, diffFlags,pRe,mperm);
1207 }
1208 db_finalize(&q3);
1209 }
1210 append_diff_javascript(diffType==2);
1211 cookie_render();
1212 style_footer();
1213 }
1214
1215 /*
1216 ** WEBPAGE: localpatch
1217 ** URL: /localpatch
1218 **
1219 ** Shows a patch from the current checkout, incorporating any
1220 ** uncommitted local edits.
1221 */
1222 void localpatch_page(void){
1223 Stmt q3;
1224 int vid;
1225
1226 login_check_credentials();
1227 if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
1228
1229 vid = g.localOpen ? db_lget_int("checkout", 0) : 0;
1230 if( vid==0 ){
1231 /*TODO Is this the right response? */
1232 style_header("No Local Checkout");
1233 @ No access to local checkout.
1234 style_footer();
1235 return;
1236 }
1237 vfile_check_signature(vid, CKSIG_ENOTFILE);
1238
1239 cgi_set_content_type("text/plain");
1240
1241 db_begin_transaction();
1242 /*TODO
1243 ** This query is the same as in ci_page() for local-mode (as well as in
1244 ** diff_against_disk() in diffcmd.c, where it was originally taken from).
1245 ** Should they be "coalesced" in some way?
1246 */
1247 db_prepare(&q3,
1248 "SELECT pathname, deleted, chnged , rid==0, rid, islink"
1249 " FROM vfile"
1250 " WHERE vid=%d"
1251 " AND (deleted OR chnged OR rid==0)"
1252 " ORDER BY pathname /*scan*/",
1253 vid
1254 );
1255 while( db_step(&q3)==SQLITE_ROW ){
1256 const char *zPathname = db_column_text(&q3,0);
1257 int isDeleted = db_column_int(&q3, 1);
1258 int isChnged = db_column_int(&q3,2);
1259 int isNew = db_column_int(&q3,3);
1260 int srcid = db_column_int(&q3, 4);
1261 int isLink = db_column_int(&q3, 5);
1262 char *zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", srcid);
1263
1264 if( isChnged ){
1265 Blob c1, c2; /* Content to diff */
1266 Blob out; /* Diff output text */
1267 int diffFlags = 4;
1268
1269 content_get(srcid, &c1);
1270 content_from_file(zPathname, &c2);
1271 blob_zero(&out);
1272 text_diff(&c1, &c2, &out, 0, diffFlags);
1273 blob_reset(&c1);
1274 blob_reset(&c2);
1275 if( blob_size(&out) ){
1276 diff_print_index(zPathname, diffFlags);
1277 diff_print_filenames(zPathname, zPathname, diffFlags);
1278 fossil_print("%s\n", blob_str(&out));
1279 }
1280 /* Release memory resources */
1281 blob_reset(&out);
1282 }
1283 free(zUuid);
1284 }
1285 db_finalize(&q3);
1286 db_end_transaction(1); /* ROLLBACK */
1287 }
1288
1289 /*
1290 ** WEBPAGE: winfo
1291 ** URL: /winfo?name=UUID
1292 **
@@ -1216,11 +1563,11 @@
1563 style_submenu_element("Side-by-Side Diff",
1564 "%R/vdiff?%s&diff=2%s%T%s",
1565 zQuery,
1566 zGlob ? "&glob=" : "", zGlob ? zGlob : "", zW);
1567 }
1568 if( diffType!=1 ){
1569 style_submenu_element("Unified Diff",
1570 "%R/vdiff?%s&diff=1%s%T%s",
1571 zQuery,
1572 zGlob ? "&glob=" : "", zGlob ? zGlob : "", zW);
1573 }
@@ -1400,11 +1747,11 @@
1747 }
1748 cnt++;
1749 continue;
1750 }
1751 if( !sameFilename ){
1752 if( prevName && showDetail ){
1753 @ </ul>
1754 }
1755 if( mPerm==PERM_LNK ){
1756 @ <li>Symbolic link
1757 objType |= OBJTYPE_SYMLINK;
@@ -1520,11 +1867,11 @@
1867 objType |= OBJTYPE_TICKET;
1868 }else if( zType[0]=='c' ){
1869 @ Manifest of check-in
1870 objType |= OBJTYPE_CHECKIN;
1871 }else if( zType[0]=='e' ){
1872 if( eventTagId != 0){
1873 @ Instance of technote
1874 objType |= OBJTYPE_EVENT;
1875 hyperlink_to_event_tagid(db_column_int(&q, 5));
1876 }else{
1877 @ Attachment to technote
@@ -1567,11 +1914,11 @@
1914 }else{
1915 @ Attachment "%h(zFilename)" to
1916 }
1917 objType |= OBJTYPE_ATTACHMENT;
1918 if( fossil_is_uuid(zTarget) ){
1919 if( db_exists("SELECT 1 FROM tag WHERE tagname='tkt-%q'",
1920 zTarget)
1921 ){
1922 if( g.perm.Hyperlink && g.anon.RdTkt ){
1923 @ ticket [%z(href("%R/tktview?name=%!S",zTarget))%S(zTarget)</a>]
1924 }else{
@@ -1625,11 +1972,13 @@
1972 }
1973
1974
1975 /*
1976 ** WEBPAGE: fdiff
1977 ** WEBPAGE: localdiff
1978 ** URL: fdiff?v1=UUID&v2=UUID
1979 ** URL: localdiff?name=filename
1980 **
1981 ** Two arguments, v1 and v2, identify the artifacts to be diffed.
1982 ** Show diff side by side unless sbs is 0. Generate plain text if
1983 ** "patch" is present, otherwise generate "pretty" HTML.
1984 **
@@ -1637,10 +1986,14 @@
1986 **
1987 ** If the "from" and "to" query parameters are both present, then they are
1988 ** the names of two files within the check-in "ci" that are diffed. If the
1989 ** "ci" parameter is omitted, then the most recent check-in ("tip") is
1990 ** used.
1991 **
1992 ** The /localdiff version will diff the given filename from the most recent
1993 ** check-in ("tip") against the current (edited) version in the checkout
1994 ** directory.
1995 **
1996 ** Additional parameters:
1997 **
1998 ** dc=N Show N lines of context around each diff
1999 ** patch Use the patch diff format
@@ -1658,17 +2011,23 @@
2011 const char *zRe;
2012 ReCompiled *pRe = 0;
2013 u64 diffFlags;
2014 u32 objdescFlags = 0;
2015 int verbose = PB("verbose");
2016 int bLocalMode = g.zPath[0]=='l'; /* diff against checkout */
2017 const char *zLocalName = NULL; /* Holds local filename */
2018
2019 login_check_credentials();
2020 if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
2021 cookie_link_parameter("diff","diff","2");
2022 diffType = atoi(PD("diff","2"));
2023 cookie_render();
2024 if( bLocalMode ){
2025 zLocalName = P("name");
2026 v1 = artifact_from_ci_and_filename("name");
2027 v2 = (zLocalName!=NULL)?-1:0; /* -1 prevents "not found" check below */
2028 }else if( P("from") && P("to") ){
2029 v1 = artifact_from_ci_and_filename("from");
2030 v2 = artifact_from_ci_and_filename("to");
2031 }else{
2032 Stmt q;
2033 v1 = name_to_rid_www("v1");
@@ -1707,14 +2066,19 @@
2066 if( zRe ) re_compile(&pRe, zRe, 0);
2067 if( verbose ) objdescFlags |= OBJDESC_DETAIL;
2068 if( isPatch ){
2069 Blob c1, c2, *pOut;
2070 pOut = cgi_output_blob();
2071
2072 cgi_set_content_type("text/plain");
2073 diffFlags = 4;
2074 content_get(v1, &c1);
2075 if( bLocalMode ){
2076 content_from_file(zLocalName, &c2);
2077 }else{
2078 content_get(v2, &c2);
2079 }
2080 text_diff(&c1, &c2, pOut, pRe, diffFlags);
2081 blob_reset(&c1);
2082 blob_reset(&c2);
2083 return;
2084 }
@@ -1723,38 +2087,62 @@
2087 zV2 = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", v2);
2088 diffFlags = construct_diff_flags(diffType) | DIFF_HTML;
2089
2090 style_header("Diff");
2091 style_submenu_checkbox("w", "Ignore Whitespace", 0, 0);
2092 if( bLocalMode ){
2093 if( diffType==2 ){
2094 style_submenu_element("Unified Diff", "%R/localdiff?name=%T&diff=1",
2095 zLocalName);
2096 }else{
2097 style_submenu_element("Side-by-side Diff", "%R/localdiff?name=%T&diff=2",
2098 zLocalName);
2099 }
2100 }else{ /* Normal */
2101 if( diffType==2 ){
2102 style_submenu_element("Unified Diff", "%R/fdiff?v1=%T&v2=%T&diff=1",
2103 P("v1"), P("v2"));
2104 }else{
2105 style_submenu_element("Side-by-side Diff", "%R/fdiff?v1=%T&v2=%T&diff=2",
2106 P("v1"), P("v2"));
2107 }
2108 }
2109 style_submenu_checkbox("verbose", "Verbose", 0, 0);
2110 if( bLocalMode ){
2111 style_submenu_element("Patch", "%R/localdiff?name=%T&patch", zLocalName);
2112 }else{
2113 style_submenu_element("Patch", "%R/fdiff?v1=%T&v2=%T&patch",
2114 P("v1"), P("v2"));
2115 }
2116
2117 if( P("smhdr")!=0 ){
2118 @ <h2>Differences From Artifact
2119 @ %z(href("%R/artifact/%!S",zV1))[%S(zV1)]</a> To
2120 if( bLocalMode ){
2121 @ %z(href("%R/local"))[Local Changes]</a> of
2122 @ %z(href("%R/file/%T?ci=ckout",zLocalName))%h(zLocalName)</a>.
2123 }else{
2124 @ %z(href("%R/artifact/%!S",zV2))[%S(zV2)]</a>.</h2>
2125 }
2126 }else{
2127 @ <h2>Differences From
2128 @ Artifact %z(href("%R/artifact/%!S",zV1))[%S(zV1)]</a>:</h2>
2129 object_description(v1, objdescFlags,0, 0);
2130 if( bLocalMode ){
2131 @ <h2>To %z(href("%R/local"))[Local Changes]</a>
2132 @ of %z(href("%R/file/%T?ci=ckout",zLocalName))%h(zLocalName)</a>.</h2>
2133 }else{
2134 @ <h2>To Artifact %z(href("%R/artifact/%!S",zV2))[%S(zV2)]</a>:</h2>
2135 object_description(v2, objdescFlags,0, 0);
2136 }
2137 }
2138 if( pRe ){
2139 @ <b>Only differences that match regular expression "%h(zRe)"
2140 @ are shown.</b>
2141 }
2142 @ <hr />
2143 append_diff(zV1, zV2, zLocalName, diffFlags, pRe);
2144 append_diff_javascript(diffType);
2145 style_footer();
2146 }
2147
2148 /*
@@ -1908,13 +2296,16 @@
2296 }
2297
2298 /*
2299 ** WEBPAGE: hexdump
2300 ** URL: /hexdump?name=ARTIFACTID
2301 ** URL: /hexdump?local=FILENAME
2302 **
2303 ** Show the complete content of a file identified by ARTIFACTID
2304 ** as preformatted text.
2305 **
2306 ** The second version does the same for FILENAME from the local checkout.
2307 **
2308 ** Other parameters:
2309 **
2310 ** verbose Show more detail when describing the object
2311 */
@@ -1922,42 +2313,61 @@
2313 int rid;
2314 Blob content;
2315 Blob downloadName;
2316 char *zUuid;
2317 u32 objdescFlags = 0;
2318 const char *zLocalName = P("local");
2319 int bLocalMode = zLocalName!=NULL;
2320
2321 rid = name_to_rid_www("name");
2322 login_check_credentials();
2323 if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
2324 if( !bLocalMode ){
2325 if( rid==0 ) fossil_redirect_home();
2326 if( g.perm.Admin ){
2327 const char *zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d",rid);
2328 if( db_exists("SELECT 1 FROM shun WHERE uuid=%Q", zUuid) ){
2329 style_submenu_element("Unshun", "%s/shun?accept=%s&sub=1#delshun",
2330 g.zTop, zUuid);
2331 }else{
2332 style_submenu_element("Shun", "%s/shun?shun=%s#addshun", g.zTop, zUuid);
2333 }
2334 }
2335 }
2336 style_header("Hex Artifact Content");
2337 /* TODO
2338 ** Could the call to style_header() be moved so these two exclusion
2339 ** blocks could be merged? I don't think any of them make sense for
2340 ** a local file.
2341 */
2342 if( !bLocalMode ){
2343 zUuid = db_text("?","SELECT uuid FROM blob WHERE rid=%d", rid);
2344 @ <h2>Artifact
2345 style_copy_button(1, "hash-ar", 0, 2, "%s", zUuid);
2346 if( g.perm.Setup ){
2347 @ (%d(rid)):</h2>
2348 }else{
2349 @ :</h2>
2350 }
2351 blob_zero(&downloadName);
2352 if( P("verbose")!=0 ) objdescFlags |= OBJDESC_DETAIL;
2353 object_description(rid, objdescFlags, 0, &downloadName);
2354 style_submenu_element("Download", "%R/raw/%s?at=%T",
2355 zUuid, file_tail(blob_str(&downloadName)));
2356 }else{
2357 @ <h2>File %z(href("%R/finfo?name=%T&m=tip",zLocalName))%h(zLocalName)</a>
2358 @ from %z(href("%R/local"))[Local Changes]</a></h2>
2359 }
 
 
 
 
 
2360 @ <hr />
2361 if( bLocalMode ){
2362 content_from_file(zLocalName, &content);
2363 }else{
2364 content_get(rid, &content);
2365 }
2366 @ <blockquote><pre>
2367 hexdump(&content);
2368 /* TODO: Should content (and downloadName?) be reset/freed? */
2369 @ </pre></blockquote>
2370 style_footer();
2371 }
2372
2373 /*
@@ -2131,10 +2541,13 @@
2541 ** if name= cannot be understood as a hash, a default "tip" value is
2542 ** used for ci=.
2543 **
2544 ** For /file, name= can only be interpreted as a filename. As before,
2545 ** a default value of "tip" is used for ci= if ci= is omitted.
2546 **
2547 ** If ci=ckout then display the content of the file NAME in the local
2548 ** checkout directory.
2549 */
2550 void artifact_page(void){
2551 int rid = 0;
2552 Blob content;
2553 const char *zMime;
@@ -2153,10 +2566,11 @@
2566 HQuery url;
2567 char *zCIUuid = 0;
2568 int isSymbolicCI = 0; /* ci= exists and is a symbolic name, not a hash */
2569 int isBranchCI = 0; /* ci= refers to a branch name */
2570 char *zHeader = 0;
2571 int bLocalMode = 0; /* TRUE if trying to show file in local checkout */
2572
2573 login_check_credentials();
2574 if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
2575
2576 /* Capture and normalize the name= and ci= query parameters */
@@ -2200,11 +2614,14 @@
2614 ** name= as a hash for /artifact and /whatis. But for not for /file.
2615 ** For /file, a name= without a ci= while prefer to use the default
2616 ** "tip" value for ci=. */
2617 rid = name_to_rid(zName);
2618 }
2619 if( fossil_strcmp(zCI,"ckout")==0 ){
2620 bLocalMode = 1;
2621 rid = -1; /* Dummy value to make it look found */
2622 }else if( rid==0 ){
2623 rid = artifact_from_ci_and_filename(0);
2624 }
2625
2626 if( rid==0 ){ /* Artifact not found */
2627 if( isFile ){
@@ -2237,11 +2654,25 @@
2654 }
2655 zUuid = db_text("?", "SELECT uuid FROM blob WHERE rid=%d", rid);
2656
2657 asText = P("txt")!=0;
2658 if( isFile ){
2659 if( bLocalMode ){
2660 /*TODO
2661 ** Is this the best way of handling annotations to the description?
2662 ** If "annot=message" is part of the URL, the message is appended
2663 ** to the description of the file. Only used for "local" files to
2664 ** distinguish such files from part of the repository.
2665 */
2666 const char *annot = P("annot");
2667 @ <h2>File %z(href("%R/finfo?name=%T&m=tip",zName))%h(zName)</a>
2668 @ from %z(href("%R/local"))[Local Changes]</a>
2669 if( annot ){
2670 @ (%h(annot))
2671 }
2672 @ </h2>
2673 }else if( zCI==0 || fossil_strcmp(zCI,"tip")==0 ){
2674 zCI = "tip";
2675 isSymbolicCI = 1; /* Mark default-to-"tip" as symbolic */
2676 @ <h2>File %z(href("%R/finfo?name=%T&m=tip",zName))%h(zName)</a>
2677 @ from the %z(href("%R/info/tip"))latest check-in</a></h2>
2678 }else{
@@ -2258,15 +2689,17 @@
2689 }else{
2690 @ as of check-in [%z(href("%R/info/%!S",zCIUuid))%S(zCIUuid)</a>]</h2>
2691 }
2692 blob_reset(&path);
2693 }
2694 if( !bLocalMode ){
2695 style_submenu_element("Artifact", "%R/artifact/%S", zUuid);
2696 style_submenu_element("Annotate", "%R/annotate?filename=%T&checkin=%T",
2697 zName, zCI);
2698 style_submenu_element("Blame", "%R/blame?filename=%T&checkin=%T",
2699 zName, zCI);
2700 }
2701 blob_init(&downloadName, zName, -1);
2702 objType = OBJTYPE_CONTENT;
2703 }else{
2704 @ <h2>Artifact
2705 style_copy_button(1, "hash-ar", 0, 2, "%s", zUuid);
@@ -2278,29 +2711,33 @@
2711 blob_zero(&downloadName);
2712 if( asText ) objdescFlags &= ~OBJDESC_BASE;
2713 objType = object_description(rid, objdescFlags,
2714 (isFile?zName:0), &downloadName);
2715 }
2716 if( !bLocalMode ){
2717 if( !descOnly && P("download")!=0 ){
2718 cgi_redirectf("%R/raw/%s?at=%T",
2719 db_text("x", "SELECT uuid FROM blob WHERE rid=%d", rid),
2720 file_tail(blob_str(&downloadName)));
2721 /*NOTREACHED*/
2722 }
2723 if( g.perm.Admin ){
2724 const char *zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d",rid);
2725 if( db_exists("SELECT 1 FROM shun WHERE uuid=%Q", zUuid) ){
2726 style_submenu_element("Unshun", "%s/shun?accept=%s&sub=1#accshun",
2727 g.zTop, zUuid);
2728 }else{
2729 style_submenu_element("Shun", "%s/shun?shun=%s#addshun", g.zTop, zUuid);
2730 }
2731 }
2732 }
2733
2734 if( isFile ){
2735 if( isSymbolicCI ){
2736 zHeader = mprintf("%s at %s", file_tail(zName), zCI);
2737 }else if( bLocalMode ){
2738 zHeader = mprintf("%s (local changes)", file_tail(zName));
2739 }else if( zCI ){
2740 zHeader = mprintf("%s at [%S]", file_tail(zName), zCIUuid);
2741 }else{
2742 zHeader = mprintf("%s", file_tail(zName));
2743 }
@@ -2326,13 +2763,17 @@
2763 const char *zIp = db_column_text(&q,2);
2764 @ <p>Received on %s(zDate) from %h(zUser) at %h(zIp).</p>
2765 }
2766 db_finalize(&q);
2767 }
2768 if( !bLocalMode ){
2769 style_submenu_element("Download", "%R/raw/%s?at=%T",
2770 zUuid, file_tail(zName));
2771 if( db_exists("SELECT 1 FROM mlink WHERE fid=%d", rid) ){
2772 style_submenu_element("Check-ins Using", "%R/timeline?n=200&uf=%s",
2773 zUuid);
2774 }
2775 }
2776 zMime = mimetype_from_name(blob_str(&downloadName));
2777 if( zMime ){
2778 if( fossil_strcmp(zMime, "text/html")==0 ){
2779 if( asText ){
@@ -2348,24 +2789,38 @@
2789 }else{
2790 renderAsWiki = 1;
2791 style_submenu_element("Text", "%s", url_render(&url, "txt", "1", 0, 0));
2792 }
2793 }
2794 if( !bLocalMode ){ /* This way madness lies... */
2795 if( fileedit_is_editable(zName) ){
2796 style_submenu_element("Edit",
2797 "%R/fileedit?filename=%T&checkin=%!S",
2798 zName, zCI);
2799 }
2800 }
2801 }
2802 if( (objType & (OBJTYPE_WIKI|OBJTYPE_TICKET))!=0 ){
2803 style_submenu_element("Parsed", "%R/info/%s", zUuid);
2804 }
2805 if( descOnly ){
2806 style_submenu_element("Content", "%R/artifact/%s", zUuid);
2807 }else{
2808 @ <hr />
2809 if( bLocalMode ){
2810 /*TODO
2811 ** Should we handle non-existent local files differently? Currently,
2812 ** they are shown the same as if the file was present but empty. This
2813 ** should never happen through "normal" operation, but someone might
2814 ** craft a link to one. Perhaps have content_from_file() perform an
2815 ** existence-check (rather than relying on blob_read_from_file() which
2816 ** it calls returning an empty blob)?
2817 */
2818 content_from_file(zName, &content);
2819 }else{
2820 content_get(rid, &content);
2821 }
2822 if( renderAsWiki ){
2823 wiki_render_by_mimetype(&content, zMime);
2824 }else if( renderAsHtml ){
2825 @ <iframe src="%R/raw/%s(zUuid)"
2826 @ width="100%%" frameborder="0" marginwidth="0" marginheight="0"
@@ -2377,11 +2832,15 @@
2832 @ this.height=this.contentDocument.documentElement.scrollHeight + 75;
2833 @ }
2834 @ );
2835 @ </script>
2836 }else{
2837 if( bLocalMode ){
2838 style_submenu_element("Hex", "%R/hexdump?local=%s", zName);
2839 }else{
2840 style_submenu_element("Hex", "%s/hexdump?name=%s", g.zTop, zUuid);
2841 }
2842 if( zLn==0 || atoi(zLn)==0 ){
2843 style_submenu_checkbox("ln", "Line Numbers", 0, 0);
2844 }
2845 blob_to_utf8_no_bom(&content, 0);
2846 zMime = mimetype_from_content(&content);
2847

Keyboard Shortcuts

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