Fossil SCM

Collected improvements to the fossil wiki and attach commands to support technotes as well as wiki pages, including attachments to either and new test cases. Passes all tests.

rberteig 2016-05-14 00:48 trunk merge
Commit 05cd9fa2bfd7656b561226945275b606f5c42786
+162 -50
--- src/attach.c
+++ src/attach.c
@@ -246,10 +246,66 @@
246246
db_multi_exec("INSERT OR IGNORE INTO unclustered VALUES(%d);", rid);
247247
}
248248
manifest_crosslink(rid, pAttach, MC_NONE);
249249
}
250250
251
+
252
+/*
253
+** Commit a new attachment into the repository
254
+*/
255
+void attach_commit(
256
+ const char *zName, /* The filename of the attachment */
257
+ const char *zTarget, /* The artifact uuid to attach to */
258
+ const char *aContent, /* The content of the attachment */
259
+ int szContent, /* The length of the attachment */
260
+ int needModerator, /* Moderate the attachment? */
261
+ const char *zComment /* The comment for the attachment */
262
+){
263
+ Blob content;
264
+ Blob manifest;
265
+ Blob cksum;
266
+ char *zUUID;
267
+ char *zDate;
268
+ int rid;
269
+ int i, n;
270
+ int addCompress = 0;
271
+ Manifest *pManifest;
272
+
273
+ db_begin_transaction();
274
+ blob_init(&content, aContent, szContent);
275
+ pManifest = manifest_parse(&content, 0, 0);
276
+ manifest_destroy(pManifest);
277
+ blob_init(&content, aContent, szContent);
278
+ if( pManifest ){
279
+ blob_compress(&content, &content);
280
+ addCompress = 1;
281
+ }
282
+ rid = content_put_ex(&content, 0, 0, 0, needModerator);
283
+ zUUID = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
284
+ blob_zero(&manifest);
285
+ for(i=n=0; zName[i]; i++){
286
+ if( zName[i]=='/' || zName[i]=='\\' ) n = i+1;
287
+ }
288
+ zName += n;
289
+ if( zName[0]==0 ) zName = "unknown";
290
+ blob_appendf(&manifest, "A %F%s %F %s\n",
291
+ zName, addCompress ? ".gz" : "", zTarget, zUUID);
292
+ while( fossil_isspace(zComment[0]) ) zComment++;
293
+ n = strlen(zComment);
294
+ while( n>0 && fossil_isspace(zComment[n-1]) ){ n--; }
295
+ if( n>0 ){
296
+ blob_appendf(&manifest, "C %#F\n", n, zComment);
297
+ }
298
+ zDate = date_in_standard_format("now");
299
+ blob_appendf(&manifest, "D %s\n", zDate);
300
+ blob_appendf(&manifest, "U %F\n", login_name());
301
+ md5sum_blob(&manifest, &cksum);
302
+ blob_appendf(&manifest, "Z %b\n", &cksum);
303
+ attach_put(&manifest, rid, needModerator);
304
+ assert( blob_is_reset(&manifest) );
305
+ db_end_transaction(0);
306
+}
251307
252308
/*
253309
** WEBPAGE: attachadd
254310
** Add a new attachment.
255311
**
@@ -300,11 +356,11 @@
300356
zTechNote = db_text(0, "SELECT substr(tagname,7) FROM tag"
301357
" WHERE tagname GLOB 'event-%q*'", zTechNote);
302358
if( zTechNote==0) fossil_redirect_home();
303359
}
304360
zTarget = zTechNote;
305
- zTargetType = mprintf("Tech Note <a href=\"%R/technote/%h\">%h</a>",
361
+ zTargetType = mprintf("Tech Note <a href=\"%R/technote/%s\">%S</a>",
306362
zTechNote, zTechNote);
307363
308364
}else{
309365
if( g.perm.ApndTkt==0 || g.perm.Attach==0 ){
310366
login_needed(g.anon.ApndTkt && g.anon.Attach);
@@ -322,59 +378,14 @@
322378
if( zFrom==0 ) zFrom = mprintf("%s/home", g.zTop);
323379
if( P("cancel") ){
324380
cgi_redirect(zFrom);
325381
}
326382
if( P("ok") && szContent>0 && (goodCaptcha = captcha_is_correct()) ){
327
- Blob content;
328
- Blob manifest;
329
- Blob cksum;
330
- char *zUUID;
331
- const char *zComment;
332
- char *zDate;
333
- int rid;
334
- int i, n;
335
- int addCompress = 0;
336
- Manifest *pManifest;
337
- int needModerator;
338
-
339
- db_begin_transaction();
340
- blob_init(&content, aContent, szContent);
341
- pManifest = manifest_parse(&content, 0, 0);
342
- manifest_destroy(pManifest);
343
- blob_init(&content, aContent, szContent);
344
- if( pManifest ){
345
- blob_compress(&content, &content);
346
- addCompress = 1;
347
- }
348
- needModerator =
349
- (zTkt!=0 && ticket_need_moderation(0)) ||
350
- (zPage!=0 && wiki_need_moderation(0));
351
- rid = content_put_ex(&content, 0, 0, 0, needModerator);
352
- zUUID = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
353
- blob_zero(&manifest);
354
- for(i=n=0; zName[i]; i++){
355
- if( zName[i]=='/' || zName[i]=='\\' ) n = i;
356
- }
357
- zName += n;
358
- if( zName[0]==0 ) zName = "unknown";
359
- blob_appendf(&manifest, "A %F%s %F %s\n",
360
- zName, addCompress ? ".gz" : "", zTarget, zUUID);
361
- zComment = PD("comment", "");
362
- while( fossil_isspace(zComment[0]) ) zComment++;
363
- n = strlen(zComment);
364
- while( n>0 && fossil_isspace(zComment[n-1]) ){ n--; }
365
- if( n>0 ){
366
- blob_appendf(&manifest, "C %#F\n", n, zComment);
367
- }
368
- zDate = date_in_standard_format("now");
369
- blob_appendf(&manifest, "D %s\n", zDate);
370
- blob_appendf(&manifest, "U %F\n", login_name());
371
- md5sum_blob(&manifest, &cksum);
372
- blob_appendf(&manifest, "Z %b\n", &cksum);
373
- attach_put(&manifest, rid, needModerator);
374
- assert( blob_is_reset(&manifest) );
375
- db_end_transaction(0);
383
+ int needModerator = (zTkt!=0 && ticket_need_moderation(0)) ||
384
+ (zPage!=0 && wiki_need_moderation(0));
385
+ const char *zComment = PD("comment", "");
386
+ attach_commit(zName, zTarget, aContent, szContent, needModerator, zComment);
376387
cgi_redirect(zFrom);
377388
}
378389
style_header("Add Attachment");
379390
if( !goodCaptcha ){
380391
@ <p class="generalError">Error: Incorrect security code.</p>
@@ -670,5 +681,106 @@
670681
@ </ul>
671682
}
672683
db_finalize(&q);
673684
674685
}
686
+
687
+/*
688
+** COMMAND: attachment*
689
+**
690
+** Usage: %fossil attachment add ?PAGENAME? FILENAME ?OPTIONS?
691
+**
692
+** Add an attachment to an existing wiki page or tech note.
693
+**
694
+** Options:
695
+** -t|--technote DATETIME Specifies the timestamp of
696
+** the technote to which the attachment
697
+** is to be made. The attachment will be
698
+** to the most recently modified tech note
699
+** with the specified timestamp.
700
+** -t|--technote TECHNOTE-ID Specifies the technote to be
701
+** updated by its technote id.
702
+**
703
+** One of PAGENAME, DATETIME or TECHNOTE-ID must be specified.
704
+*/
705
+void attachment_cmd(void){
706
+ int n;
707
+ db_find_and_open_repository(0, 0);
708
+ if( g.argc<3 ){
709
+ goto attachment_cmd_usage;
710
+ }
711
+ n = strlen(g.argv[2]);
712
+ if( n==0 ){
713
+ goto attachment_cmd_usage;
714
+ }
715
+
716
+ if( strncmp(g.argv[2],"add",n)==0 ){
717
+ const char *zPageName; /* Name of the wiki page to attach to */
718
+ const char *zFile; /* Name of the file to be attached */
719
+ const char *zETime; /* The name of the technote to attach to */
720
+ Manifest *pWiki = 0; /* Parsed wiki page content */
721
+ char *zBody = 0; /* Wiki page content */
722
+ int rid;
723
+ const char *zTarget; /* Target of the attachment */
724
+ Blob content; /* The content of the attachment */
725
+ zETime = find_option("technote","t",1);
726
+ if( !zETime ){
727
+ if( g.argc!=5 ){
728
+ usage("add PAGENAME FILENAME");
729
+ }
730
+ zPageName = g.argv[3];
731
+ rid = db_int(0, "SELECT x.rid FROM tag t, tagxref x"
732
+ " WHERE x.tagid=t.tagid AND t.tagname='wiki-%q'"
733
+ " ORDER BY x.mtime DESC LIMIT 1",
734
+ zPageName
735
+ );
736
+ if( (pWiki = manifest_get(rid, CFTYPE_WIKI, 0))!=0 ){
737
+ zBody = pWiki->zWiki;
738
+ }
739
+ if( zBody==0 ){
740
+ fossil_fatal("wiki page [%s] not found",zPageName);
741
+ }
742
+ zTarget = zPageName;
743
+ zFile = g.argv[4];
744
+ }else{
745
+ if( g.argc!=4 ){
746
+ usage("add FILENAME --technote DATETIME|TECHNOTE-ID");
747
+ }
748
+ rid = wiki_technote_to_rid(zETime);
749
+ if( rid<0 ){
750
+ fossil_fatal("ambiguous tech note id: %s", zETime);
751
+ }
752
+ if( (pWiki = manifest_get(rid, CFTYPE_EVENT, 0))!=0 ){
753
+ zBody = pWiki->zWiki;
754
+ }
755
+ if( zBody==0 ){
756
+ fossil_fatal("technote [%s] not found",zETime);
757
+ }
758
+ zTarget = db_text(0,
759
+ "SELECT substr(tagname,7) FROM tag WHERE tagid=(SELECT tagid FROM event WHERE objid='%d')",
760
+ rid
761
+ );
762
+ zFile = g.argv[3];
763
+ }
764
+ blob_read_from_file(&content, zFile);
765
+ user_select();
766
+ attach_commit(
767
+ zFile, /* The filename of the attachment */
768
+ zTarget, /* The artifact uuid to attach to */
769
+ blob_buffer(&content), /* The content of the attachment */
770
+ blob_size(&content), /* The length of the attachment */
771
+ 0, /* No need to moderate the attachment */
772
+ "" /* Empty attachment comment */
773
+ );
774
+ if( !zETime ){
775
+ fossil_print("Attached %s to wiki page %s.\n", zFile, zPageName);
776
+ }else{
777
+ fossil_print("Attached %s to tech note %s.\n", zFile, zETime);
778
+ }
779
+ }else{
780
+ goto attachment_cmd_usage;
781
+ }
782
+ return;
783
+
784
+attachment_cmd_usage:
785
+ usage("add ?PAGENAME? FILENAME [-t|--technote DATETIME ]");
786
+}
675787
--- src/attach.c
+++ src/attach.c
@@ -246,10 +246,66 @@
246 db_multi_exec("INSERT OR IGNORE INTO unclustered VALUES(%d);", rid);
247 }
248 manifest_crosslink(rid, pAttach, MC_NONE);
249 }
250
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
251
252 /*
253 ** WEBPAGE: attachadd
254 ** Add a new attachment.
255 **
@@ -300,11 +356,11 @@
300 zTechNote = db_text(0, "SELECT substr(tagname,7) FROM tag"
301 " WHERE tagname GLOB 'event-%q*'", zTechNote);
302 if( zTechNote==0) fossil_redirect_home();
303 }
304 zTarget = zTechNote;
305 zTargetType = mprintf("Tech Note <a href=\"%R/technote/%h\">%h</a>",
306 zTechNote, zTechNote);
307
308 }else{
309 if( g.perm.ApndTkt==0 || g.perm.Attach==0 ){
310 login_needed(g.anon.ApndTkt && g.anon.Attach);
@@ -322,59 +378,14 @@
322 if( zFrom==0 ) zFrom = mprintf("%s/home", g.zTop);
323 if( P("cancel") ){
324 cgi_redirect(zFrom);
325 }
326 if( P("ok") && szContent>0 && (goodCaptcha = captcha_is_correct()) ){
327 Blob content;
328 Blob manifest;
329 Blob cksum;
330 char *zUUID;
331 const char *zComment;
332 char *zDate;
333 int rid;
334 int i, n;
335 int addCompress = 0;
336 Manifest *pManifest;
337 int needModerator;
338
339 db_begin_transaction();
340 blob_init(&content, aContent, szContent);
341 pManifest = manifest_parse(&content, 0, 0);
342 manifest_destroy(pManifest);
343 blob_init(&content, aContent, szContent);
344 if( pManifest ){
345 blob_compress(&content, &content);
346 addCompress = 1;
347 }
348 needModerator =
349 (zTkt!=0 && ticket_need_moderation(0)) ||
350 (zPage!=0 && wiki_need_moderation(0));
351 rid = content_put_ex(&content, 0, 0, 0, needModerator);
352 zUUID = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
353 blob_zero(&manifest);
354 for(i=n=0; zName[i]; i++){
355 if( zName[i]=='/' || zName[i]=='\\' ) n = i;
356 }
357 zName += n;
358 if( zName[0]==0 ) zName = "unknown";
359 blob_appendf(&manifest, "A %F%s %F %s\n",
360 zName, addCompress ? ".gz" : "", zTarget, zUUID);
361 zComment = PD("comment", "");
362 while( fossil_isspace(zComment[0]) ) zComment++;
363 n = strlen(zComment);
364 while( n>0 && fossil_isspace(zComment[n-1]) ){ n--; }
365 if( n>0 ){
366 blob_appendf(&manifest, "C %#F\n", n, zComment);
367 }
368 zDate = date_in_standard_format("now");
369 blob_appendf(&manifest, "D %s\n", zDate);
370 blob_appendf(&manifest, "U %F\n", login_name());
371 md5sum_blob(&manifest, &cksum);
372 blob_appendf(&manifest, "Z %b\n", &cksum);
373 attach_put(&manifest, rid, needModerator);
374 assert( blob_is_reset(&manifest) );
375 db_end_transaction(0);
376 cgi_redirect(zFrom);
377 }
378 style_header("Add Attachment");
379 if( !goodCaptcha ){
380 @ <p class="generalError">Error: Incorrect security code.</p>
@@ -670,5 +681,106 @@
670 @ </ul>
671 }
672 db_finalize(&q);
673
674 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
675
--- src/attach.c
+++ src/attach.c
@@ -246,10 +246,66 @@
246 db_multi_exec("INSERT OR IGNORE INTO unclustered VALUES(%d);", rid);
247 }
248 manifest_crosslink(rid, pAttach, MC_NONE);
249 }
250
251
252 /*
253 ** Commit a new attachment into the repository
254 */
255 void attach_commit(
256 const char *zName, /* The filename of the attachment */
257 const char *zTarget, /* The artifact uuid to attach to */
258 const char *aContent, /* The content of the attachment */
259 int szContent, /* The length of the attachment */
260 int needModerator, /* Moderate the attachment? */
261 const char *zComment /* The comment for the attachment */
262 ){
263 Blob content;
264 Blob manifest;
265 Blob cksum;
266 char *zUUID;
267 char *zDate;
268 int rid;
269 int i, n;
270 int addCompress = 0;
271 Manifest *pManifest;
272
273 db_begin_transaction();
274 blob_init(&content, aContent, szContent);
275 pManifest = manifest_parse(&content, 0, 0);
276 manifest_destroy(pManifest);
277 blob_init(&content, aContent, szContent);
278 if( pManifest ){
279 blob_compress(&content, &content);
280 addCompress = 1;
281 }
282 rid = content_put_ex(&content, 0, 0, 0, needModerator);
283 zUUID = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
284 blob_zero(&manifest);
285 for(i=n=0; zName[i]; i++){
286 if( zName[i]=='/' || zName[i]=='\\' ) n = i+1;
287 }
288 zName += n;
289 if( zName[0]==0 ) zName = "unknown";
290 blob_appendf(&manifest, "A %F%s %F %s\n",
291 zName, addCompress ? ".gz" : "", zTarget, zUUID);
292 while( fossil_isspace(zComment[0]) ) zComment++;
293 n = strlen(zComment);
294 while( n>0 && fossil_isspace(zComment[n-1]) ){ n--; }
295 if( n>0 ){
296 blob_appendf(&manifest, "C %#F\n", n, zComment);
297 }
298 zDate = date_in_standard_format("now");
299 blob_appendf(&manifest, "D %s\n", zDate);
300 blob_appendf(&manifest, "U %F\n", login_name());
301 md5sum_blob(&manifest, &cksum);
302 blob_appendf(&manifest, "Z %b\n", &cksum);
303 attach_put(&manifest, rid, needModerator);
304 assert( blob_is_reset(&manifest) );
305 db_end_transaction(0);
306 }
307
308 /*
309 ** WEBPAGE: attachadd
310 ** Add a new attachment.
311 **
@@ -300,11 +356,11 @@
356 zTechNote = db_text(0, "SELECT substr(tagname,7) FROM tag"
357 " WHERE tagname GLOB 'event-%q*'", zTechNote);
358 if( zTechNote==0) fossil_redirect_home();
359 }
360 zTarget = zTechNote;
361 zTargetType = mprintf("Tech Note <a href=\"%R/technote/%s\">%S</a>",
362 zTechNote, zTechNote);
363
364 }else{
365 if( g.perm.ApndTkt==0 || g.perm.Attach==0 ){
366 login_needed(g.anon.ApndTkt && g.anon.Attach);
@@ -322,59 +378,14 @@
378 if( zFrom==0 ) zFrom = mprintf("%s/home", g.zTop);
379 if( P("cancel") ){
380 cgi_redirect(zFrom);
381 }
382 if( P("ok") && szContent>0 && (goodCaptcha = captcha_is_correct()) ){
383 int needModerator = (zTkt!=0 && ticket_need_moderation(0)) ||
384 (zPage!=0 && wiki_need_moderation(0));
385 const char *zComment = PD("comment", "");
386 attach_commit(zName, zTarget, aContent, szContent, needModerator, zComment);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
387 cgi_redirect(zFrom);
388 }
389 style_header("Add Attachment");
390 if( !goodCaptcha ){
391 @ <p class="generalError">Error: Incorrect security code.</p>
@@ -670,5 +681,106 @@
681 @ </ul>
682 }
683 db_finalize(&q);
684
685 }
686
687 /*
688 ** COMMAND: attachment*
689 **
690 ** Usage: %fossil attachment add ?PAGENAME? FILENAME ?OPTIONS?
691 **
692 ** Add an attachment to an existing wiki page or tech note.
693 **
694 ** Options:
695 ** -t|--technote DATETIME Specifies the timestamp of
696 ** the technote to which the attachment
697 ** is to be made. The attachment will be
698 ** to the most recently modified tech note
699 ** with the specified timestamp.
700 ** -t|--technote TECHNOTE-ID Specifies the technote to be
701 ** updated by its technote id.
702 **
703 ** One of PAGENAME, DATETIME or TECHNOTE-ID must be specified.
704 */
705 void attachment_cmd(void){
706 int n;
707 db_find_and_open_repository(0, 0);
708 if( g.argc<3 ){
709 goto attachment_cmd_usage;
710 }
711 n = strlen(g.argv[2]);
712 if( n==0 ){
713 goto attachment_cmd_usage;
714 }
715
716 if( strncmp(g.argv[2],"add",n)==0 ){
717 const char *zPageName; /* Name of the wiki page to attach to */
718 const char *zFile; /* Name of the file to be attached */
719 const char *zETime; /* The name of the technote to attach to */
720 Manifest *pWiki = 0; /* Parsed wiki page content */
721 char *zBody = 0; /* Wiki page content */
722 int rid;
723 const char *zTarget; /* Target of the attachment */
724 Blob content; /* The content of the attachment */
725 zETime = find_option("technote","t",1);
726 if( !zETime ){
727 if( g.argc!=5 ){
728 usage("add PAGENAME FILENAME");
729 }
730 zPageName = g.argv[3];
731 rid = db_int(0, "SELECT x.rid FROM tag t, tagxref x"
732 " WHERE x.tagid=t.tagid AND t.tagname='wiki-%q'"
733 " ORDER BY x.mtime DESC LIMIT 1",
734 zPageName
735 );
736 if( (pWiki = manifest_get(rid, CFTYPE_WIKI, 0))!=0 ){
737 zBody = pWiki->zWiki;
738 }
739 if( zBody==0 ){
740 fossil_fatal("wiki page [%s] not found",zPageName);
741 }
742 zTarget = zPageName;
743 zFile = g.argv[4];
744 }else{
745 if( g.argc!=4 ){
746 usage("add FILENAME --technote DATETIME|TECHNOTE-ID");
747 }
748 rid = wiki_technote_to_rid(zETime);
749 if( rid<0 ){
750 fossil_fatal("ambiguous tech note id: %s", zETime);
751 }
752 if( (pWiki = manifest_get(rid, CFTYPE_EVENT, 0))!=0 ){
753 zBody = pWiki->zWiki;
754 }
755 if( zBody==0 ){
756 fossil_fatal("technote [%s] not found",zETime);
757 }
758 zTarget = db_text(0,
759 "SELECT substr(tagname,7) FROM tag WHERE tagid=(SELECT tagid FROM event WHERE objid='%d')",
760 rid
761 );
762 zFile = g.argv[3];
763 }
764 blob_read_from_file(&content, zFile);
765 user_select();
766 attach_commit(
767 zFile, /* The filename of the attachment */
768 zTarget, /* The artifact uuid to attach to */
769 blob_buffer(&content), /* The content of the attachment */
770 blob_size(&content), /* The length of the attachment */
771 0, /* No need to moderate the attachment */
772 "" /* Empty attachment comment */
773 );
774 if( !zETime ){
775 fossil_print("Attached %s to wiki page %s.\n", zFile, zPageName);
776 }else{
777 fossil_print("Attached %s to tech note %s.\n", zFile, zETime);
778 }
779 }else{
780 goto attachment_cmd_usage;
781 }
782 return;
783
784 attachment_cmd_usage:
785 usage("add ?PAGENAME? FILENAME [-t|--technote DATETIME ]");
786 }
787
+4 -22
--- src/event.c
+++ src/event.c
@@ -540,44 +540,26 @@
540540
style_footer();
541541
}
542542
543543
/*
544544
** Add a new tech note to the repository. The timestamp is
545
-** given by the zETime parameter. isNew must be true to create
545
+** given by the zETime parameter. rid must be zero to create
546546
** a new page. If no previous page with the name zPageName exists
547547
** and isNew is false, then this routine throws an error.
548548
*/
549549
void event_cmd_commit(
550550
char *zETime, /* timestamp */
551
- int isNew, /* true to create a new page */
551
+ int rid, /* Artifact id of the tech note */
552552
Blob *pContent, /* content of the new page */
553553
const char *zMimeType, /* mimetype of the content */
554554
const char *zComment, /* comment to go on the timeline */
555555
const char *zTags, /* tags */
556556
const char *zClr /* background color */
557557
){
558
- int rid; /* Artifact id of the tech note */
559558
const char *zId; /* id of the tech note */
560
- rid = db_int(0, "SELECT objid FROM event"
561
- " WHERE datetime(mtime)=datetime('%q') AND type = 'e'"
562
- " LIMIT 1",
563
- zETime
564
- );
565
- if( rid==0 && !isNew ){
566
-#ifdef FOSSIL_ENABLE_JSON
567
- g.json.resultCode = FSL_JSON_E_RESOURCE_NOT_FOUND;
568
-#endif
569
- fossil_fatal("no such tech note: %s", zETime);
570
- }
571
- if( rid!=0 && isNew ){
572
-#ifdef FOSSIL_ENABLE_JSON
573
- g.json.resultCode = FSL_JSON_E_RESOURCE_ALREADY_EXISTS;
574
-#endif
575
- fossil_fatal("tech note %s already exists", zETime);
576
- }
577
-
578
- if ( isNew ){
559
+
560
+ if ( rid==0 ){
579561
zId = db_text(0, "SELECT lower(hex(randomblob(20)))");
580562
}else{
581563
zId = db_text(0,
582564
"SELECT substr(tagname,7) FROM tag"
583565
" WHERE tagid=(SELECT tagid FROM event WHERE objid='%d')",
584566
--- src/event.c
+++ src/event.c
@@ -540,44 +540,26 @@
540 style_footer();
541 }
542
543 /*
544 ** Add a new tech note to the repository. The timestamp is
545 ** given by the zETime parameter. isNew must be true to create
546 ** a new page. If no previous page with the name zPageName exists
547 ** and isNew is false, then this routine throws an error.
548 */
549 void event_cmd_commit(
550 char *zETime, /* timestamp */
551 int isNew, /* true to create a new page */
552 Blob *pContent, /* content of the new page */
553 const char *zMimeType, /* mimetype of the content */
554 const char *zComment, /* comment to go on the timeline */
555 const char *zTags, /* tags */
556 const char *zClr /* background color */
557 ){
558 int rid; /* Artifact id of the tech note */
559 const char *zId; /* id of the tech note */
560 rid = db_int(0, "SELECT objid FROM event"
561 " WHERE datetime(mtime)=datetime('%q') AND type = 'e'"
562 " LIMIT 1",
563 zETime
564 );
565 if( rid==0 && !isNew ){
566 #ifdef FOSSIL_ENABLE_JSON
567 g.json.resultCode = FSL_JSON_E_RESOURCE_NOT_FOUND;
568 #endif
569 fossil_fatal("no such tech note: %s", zETime);
570 }
571 if( rid!=0 && isNew ){
572 #ifdef FOSSIL_ENABLE_JSON
573 g.json.resultCode = FSL_JSON_E_RESOURCE_ALREADY_EXISTS;
574 #endif
575 fossil_fatal("tech note %s already exists", zETime);
576 }
577
578 if ( isNew ){
579 zId = db_text(0, "SELECT lower(hex(randomblob(20)))");
580 }else{
581 zId = db_text(0,
582 "SELECT substr(tagname,7) FROM tag"
583 " WHERE tagid=(SELECT tagid FROM event WHERE objid='%d')",
584
--- src/event.c
+++ src/event.c
@@ -540,44 +540,26 @@
540 style_footer();
541 }
542
543 /*
544 ** Add a new tech note to the repository. The timestamp is
545 ** given by the zETime parameter. rid must be zero to create
546 ** a new page. If no previous page with the name zPageName exists
547 ** and isNew is false, then this routine throws an error.
548 */
549 void event_cmd_commit(
550 char *zETime, /* timestamp */
551 int rid, /* Artifact id of the tech note */
552 Blob *pContent, /* content of the new page */
553 const char *zMimeType, /* mimetype of the content */
554 const char *zComment, /* comment to go on the timeline */
555 const char *zTags, /* tags */
556 const char *zClr /* background color */
557 ){
 
558 const char *zId; /* id of the tech note */
559
560 if ( rid==0 ){
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
561 zId = db_text(0, "SELECT lower(hex(randomblob(20)))");
562 }else{
563 zId = db_text(0,
564 "SELECT substr(tagname,7) FROM tag"
565 " WHERE tagid=(SELECT tagid FROM event WHERE objid='%d')",
566
+30 -7
--- src/info.c
+++ src/info.c
@@ -1330,10 +1330,11 @@
13301330
const char *zDate = db_column_text(&q, 0);
13311331
const char *zUser = db_column_text(&q, 1);
13321332
const char *zCom = db_column_text(&q, 2);
13331333
const char *zType = db_column_text(&q, 3);
13341334
const char *zUuid = db_column_text(&q, 4);
1335
+ int eventTagId = db_column_int(&q, 5);
13351336
if( cnt>0 ){
13361337
@ Also
13371338
}
13381339
if( zType[0]=='w' ){
13391340
@ Wiki edit
@@ -1343,17 +1344,21 @@
13431344
objType |= OBJTYPE_TICKET;
13441345
}else if( zType[0]=='c' ){
13451346
@ Manifest of check-in
13461347
objType |= OBJTYPE_CHECKIN;
13471348
}else if( zType[0]=='e' ){
1348
- @ Instance of technote
1349
- objType |= OBJTYPE_EVENT;
1350
- hyperlink_to_event_tagid(db_column_int(&q, 5));
1349
+ if( eventTagId != 0) {
1350
+ @ Instance of technote
1351
+ objType |= OBJTYPE_EVENT;
1352
+ hyperlink_to_event_tagid(db_column_int(&q, 5));
1353
+ }else{
1354
+ @ Attachment to technote
1355
+ }
13511356
}else{
13521357
@ Tag referencing
13531358
}
1354
- if( zType[0]!='e' ){
1359
+ if( zType[0]!='e' || eventTagId == 0){
13551360
hyperlink_to_uuid(zUuid);
13561361
}
13571362
@ - %!W(zCom) by
13581363
hyperlink_to_user(zUser,zDate," on");
13591364
hyperlink_to_date(zDate, ".");
@@ -1383,14 +1388,32 @@
13831388
}else{
13841389
@ Attachment "%h(zFilename)" to
13851390
}
13861391
objType |= OBJTYPE_ATTACHMENT;
13871392
if( strlen(zTarget)==UUID_SIZE && validate16(zTarget,UUID_SIZE) ){
1388
- if( g.perm.Hyperlink && g.anon.RdTkt ){
1389
- @ ticket [%z(href("%R/tktview?name=%!S",zTarget))%S(zTarget)</a>]
1393
+ if ( db_exists("SELECT 1 FROM tag WHERE tagname='tkt-%q'",
1394
+ zTarget)
1395
+ ){
1396
+ if( g.perm.Hyperlink && g.anon.RdTkt ){
1397
+ @ ticket [%z(href("%R/tktview?name=%!S",zTarget))%S(zTarget)</a>]
1398
+ }else{
1399
+ @ ticket [%S(zTarget)]
1400
+ }
1401
+ }else if( db_exists("SELECT 1 FROM tag WHERE tagname='event-%q'",
1402
+ zTarget)
1403
+ ){
1404
+ if( g.perm.Hyperlink && g.anon.RdWiki ){
1405
+ @ tech note [%z(href("%R/technote/%h",zTarget))%S(zTarget)</a>]
1406
+ }else{
1407
+ @ tech note [%S(zTarget)]
1408
+ }
13901409
}else{
1391
- @ ticket [%S(zTarget)]
1410
+ if( g.perm.Hyperlink && g.anon.RdWiki ){
1411
+ @ wiki page [%z(href("%R/wiki?name=%t",zTarget))%h(zTarget)</a>]
1412
+ }else{
1413
+ @ wiki page [%h(zTarget)]
1414
+ }
13921415
}
13931416
}else{
13941417
if( g.perm.Hyperlink && g.anon.RdWiki ){
13951418
@ wiki page [%z(href("%R/wiki?name=%t",zTarget))%h(zTarget)</a>]
13961419
}else{
13971420
--- src/info.c
+++ src/info.c
@@ -1330,10 +1330,11 @@
1330 const char *zDate = db_column_text(&q, 0);
1331 const char *zUser = db_column_text(&q, 1);
1332 const char *zCom = db_column_text(&q, 2);
1333 const char *zType = db_column_text(&q, 3);
1334 const char *zUuid = db_column_text(&q, 4);
 
1335 if( cnt>0 ){
1336 @ Also
1337 }
1338 if( zType[0]=='w' ){
1339 @ Wiki edit
@@ -1343,17 +1344,21 @@
1343 objType |= OBJTYPE_TICKET;
1344 }else if( zType[0]=='c' ){
1345 @ Manifest of check-in
1346 objType |= OBJTYPE_CHECKIN;
1347 }else if( zType[0]=='e' ){
1348 @ Instance of technote
1349 objType |= OBJTYPE_EVENT;
1350 hyperlink_to_event_tagid(db_column_int(&q, 5));
 
 
 
 
1351 }else{
1352 @ Tag referencing
1353 }
1354 if( zType[0]!='e' ){
1355 hyperlink_to_uuid(zUuid);
1356 }
1357 @ - %!W(zCom) by
1358 hyperlink_to_user(zUser,zDate," on");
1359 hyperlink_to_date(zDate, ".");
@@ -1383,14 +1388,32 @@
1383 }else{
1384 @ Attachment "%h(zFilename)" to
1385 }
1386 objType |= OBJTYPE_ATTACHMENT;
1387 if( strlen(zTarget)==UUID_SIZE && validate16(zTarget,UUID_SIZE) ){
1388 if( g.perm.Hyperlink && g.anon.RdTkt ){
1389 @ ticket [%z(href("%R/tktview?name=%!S",zTarget))%S(zTarget)</a>]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1390 }else{
1391 @ ticket [%S(zTarget)]
 
 
 
 
1392 }
1393 }else{
1394 if( g.perm.Hyperlink && g.anon.RdWiki ){
1395 @ wiki page [%z(href("%R/wiki?name=%t",zTarget))%h(zTarget)</a>]
1396 }else{
1397
--- src/info.c
+++ src/info.c
@@ -1330,10 +1330,11 @@
1330 const char *zDate = db_column_text(&q, 0);
1331 const char *zUser = db_column_text(&q, 1);
1332 const char *zCom = db_column_text(&q, 2);
1333 const char *zType = db_column_text(&q, 3);
1334 const char *zUuid = db_column_text(&q, 4);
1335 int eventTagId = db_column_int(&q, 5);
1336 if( cnt>0 ){
1337 @ Also
1338 }
1339 if( zType[0]=='w' ){
1340 @ Wiki edit
@@ -1343,17 +1344,21 @@
1344 objType |= OBJTYPE_TICKET;
1345 }else if( zType[0]=='c' ){
1346 @ Manifest of check-in
1347 objType |= OBJTYPE_CHECKIN;
1348 }else if( zType[0]=='e' ){
1349 if( eventTagId != 0) {
1350 @ Instance of technote
1351 objType |= OBJTYPE_EVENT;
1352 hyperlink_to_event_tagid(db_column_int(&q, 5));
1353 }else{
1354 @ Attachment to technote
1355 }
1356 }else{
1357 @ Tag referencing
1358 }
1359 if( zType[0]!='e' || eventTagId == 0){
1360 hyperlink_to_uuid(zUuid);
1361 }
1362 @ - %!W(zCom) by
1363 hyperlink_to_user(zUser,zDate," on");
1364 hyperlink_to_date(zDate, ".");
@@ -1383,14 +1388,32 @@
1388 }else{
1389 @ Attachment "%h(zFilename)" to
1390 }
1391 objType |= OBJTYPE_ATTACHMENT;
1392 if( strlen(zTarget)==UUID_SIZE && validate16(zTarget,UUID_SIZE) ){
1393 if ( db_exists("SELECT 1 FROM tag WHERE tagname='tkt-%q'",
1394 zTarget)
1395 ){
1396 if( g.perm.Hyperlink && g.anon.RdTkt ){
1397 @ ticket [%z(href("%R/tktview?name=%!S",zTarget))%S(zTarget)</a>]
1398 }else{
1399 @ ticket [%S(zTarget)]
1400 }
1401 }else if( db_exists("SELECT 1 FROM tag WHERE tagname='event-%q'",
1402 zTarget)
1403 ){
1404 if( g.perm.Hyperlink && g.anon.RdWiki ){
1405 @ tech note [%z(href("%R/technote/%h",zTarget))%S(zTarget)</a>]
1406 }else{
1407 @ tech note [%S(zTarget)]
1408 }
1409 }else{
1410 if( g.perm.Hyperlink && g.anon.RdWiki ){
1411 @ wiki page [%z(href("%R/wiki?name=%t",zTarget))%h(zTarget)</a>]
1412 }else{
1413 @ wiki page [%h(zTarget)]
1414 }
1415 }
1416 }else{
1417 if( g.perm.Hyperlink && g.anon.RdWiki ){
1418 @ wiki page [%z(href("%R/wiki?name=%t",zTarget))%h(zTarget)</a>]
1419 }else{
1420
+2 -1
--- src/json_wiki.c
+++ src/json_wiki.c
@@ -374,12 +374,13 @@
374374
if(contentLen){
375375
blob_append(&content, cson_string_cstr(jstr),contentLen);
376376
}
377377
378378
zMimeType = json_find_option_cstr("mimetype","mimetype","M");
379
+ zMimeType = wiki_filter_mimetypes(zMimeType);
379380
380
- wiki_cmd_commit(zPageName, 0==rid, &content, zMimeType, 0);
381
+ wiki_cmd_commit(zPageName, rid, &content, zMimeType, 0);
381382
blob_reset(&content);
382383
/*
383384
Our return value here has a race condition: if this operation
384385
is called concurrently for the same wiki page via two requests,
385386
payV could reflect the results of the other save operation.
386387
--- src/json_wiki.c
+++ src/json_wiki.c
@@ -374,12 +374,13 @@
374 if(contentLen){
375 blob_append(&content, cson_string_cstr(jstr),contentLen);
376 }
377
378 zMimeType = json_find_option_cstr("mimetype","mimetype","M");
 
379
380 wiki_cmd_commit(zPageName, 0==rid, &content, zMimeType, 0);
381 blob_reset(&content);
382 /*
383 Our return value here has a race condition: if this operation
384 is called concurrently for the same wiki page via two requests,
385 payV could reflect the results of the other save operation.
386
--- src/json_wiki.c
+++ src/json_wiki.c
@@ -374,12 +374,13 @@
374 if(contentLen){
375 blob_append(&content, cson_string_cstr(jstr),contentLen);
376 }
377
378 zMimeType = json_find_option_cstr("mimetype","mimetype","M");
379 zMimeType = wiki_filter_mimetypes(zMimeType);
380
381 wiki_cmd_commit(zPageName, rid, &content, zMimeType, 0);
382 blob_reset(&content);
383 /*
384 Our return value here has a race condition: if this operation
385 is called concurrently for the same wiki page via two requests,
386 payV could reflect the results of the other save operation.
387
+11 -6
--- src/manifest.c
+++ src/manifest.c
@@ -2066,15 +2066,17 @@
20662066
const char isAdd = (zSrc && zSrc[0]) ? 1 : 0;
20672067
char *zComment;
20682068
if( isAdd ){
20692069
zComment = mprintf(
20702070
"Add attachment [/artifact/%!S|%h] to"
2071
- " tech note [/technote/%h|%.10h]",
2071
+ " tech note [/technote/%!S|%S]",
20722072
zSrc, zName, zTarget, zTarget);
20732073
}else{
2074
- zComment = mprintf("Delete attachment \"%h\" from tech note [%.10h]",
2075
- zName, zTarget);
2074
+ zComment = mprintf(
2075
+ "Delete attachment \"%h\" from"
2076
+ " tech note [/technote/%!S|%S]",
2077
+ zName, zTarget, zTarget);
20762078
}
20772079
db_multi_exec("UPDATE event SET comment=%Q, type='e'"
20782080
" WHERE objid=%Q",
20792081
zComment, zAttachId);
20802082
fossil_free(zComment);
@@ -2162,15 +2164,18 @@
21622164
p->zAttachName, p->zAttachTarget);
21632165
}
21642166
}else if( 'e' == attachToType ){
21652167
if( isAdd ){
21662168
zComment = mprintf(
2167
- "Add attachment [/artifact/%!S|%h] to tech note [/technote/%h|%.10h]",
2169
+ "Add attachment [/artifact/%!S|%h] to tech note [/technote/%!S|%S]",
21682170
p->zAttachSrc, p->zAttachName, p->zAttachTarget, p->zAttachTarget);
21692171
}else{
2170
- zComment = mprintf("Delete attachment \"%h\" from tech note [%.10h]",
2171
- p->zAttachName, p->zAttachTarget);
2172
+ zComment = mprintf(
2173
+ "Delete attachment \"/artifact/%!S|%h\" from"
2174
+ " tech note [/technote/%!S|%S]",
2175
+ p->zAttachName, p->zAttachName,
2176
+ p->zAttachTarget,p->zAttachTarget);
21722177
}
21732178
}else{
21742179
if( isAdd ){
21752180
zComment = mprintf(
21762181
"Add attachment [/artifact/%!S|%h] to ticket [%!S|%S]",
21772182
--- src/manifest.c
+++ src/manifest.c
@@ -2066,15 +2066,17 @@
2066 const char isAdd = (zSrc && zSrc[0]) ? 1 : 0;
2067 char *zComment;
2068 if( isAdd ){
2069 zComment = mprintf(
2070 "Add attachment [/artifact/%!S|%h] to"
2071 " tech note [/technote/%h|%.10h]",
2072 zSrc, zName, zTarget, zTarget);
2073 }else{
2074 zComment = mprintf("Delete attachment \"%h\" from tech note [%.10h]",
2075 zName, zTarget);
 
 
2076 }
2077 db_multi_exec("UPDATE event SET comment=%Q, type='e'"
2078 " WHERE objid=%Q",
2079 zComment, zAttachId);
2080 fossil_free(zComment);
@@ -2162,15 +2164,18 @@
2162 p->zAttachName, p->zAttachTarget);
2163 }
2164 }else if( 'e' == attachToType ){
2165 if( isAdd ){
2166 zComment = mprintf(
2167 "Add attachment [/artifact/%!S|%h] to tech note [/technote/%h|%.10h]",
2168 p->zAttachSrc, p->zAttachName, p->zAttachTarget, p->zAttachTarget);
2169 }else{
2170 zComment = mprintf("Delete attachment \"%h\" from tech note [%.10h]",
2171 p->zAttachName, p->zAttachTarget);
 
 
 
2172 }
2173 }else{
2174 if( isAdd ){
2175 zComment = mprintf(
2176 "Add attachment [/artifact/%!S|%h] to ticket [%!S|%S]",
2177
--- src/manifest.c
+++ src/manifest.c
@@ -2066,15 +2066,17 @@
2066 const char isAdd = (zSrc && zSrc[0]) ? 1 : 0;
2067 char *zComment;
2068 if( isAdd ){
2069 zComment = mprintf(
2070 "Add attachment [/artifact/%!S|%h] to"
2071 " tech note [/technote/%!S|%S]",
2072 zSrc, zName, zTarget, zTarget);
2073 }else{
2074 zComment = mprintf(
2075 "Delete attachment \"%h\" from"
2076 " tech note [/technote/%!S|%S]",
2077 zName, zTarget, zTarget);
2078 }
2079 db_multi_exec("UPDATE event SET comment=%Q, type='e'"
2080 " WHERE objid=%Q",
2081 zComment, zAttachId);
2082 fossil_free(zComment);
@@ -2162,15 +2164,18 @@
2164 p->zAttachName, p->zAttachTarget);
2165 }
2166 }else if( 'e' == attachToType ){
2167 if( isAdd ){
2168 zComment = mprintf(
2169 "Add attachment [/artifact/%!S|%h] to tech note [/technote/%!S|%S]",
2170 p->zAttachSrc, p->zAttachName, p->zAttachTarget, p->zAttachTarget);
2171 }else{
2172 zComment = mprintf(
2173 "Delete attachment \"/artifact/%!S|%h\" from"
2174 " tech note [/technote/%!S|%S]",
2175 p->zAttachName, p->zAttachName,
2176 p->zAttachTarget,p->zAttachTarget);
2177 }
2178 }else{
2179 if( isAdd ){
2180 zComment = mprintf(
2181 "Add attachment [/artifact/%!S|%h] to ticket [%!S|%S]",
2182
+162 -86
--- src/wiki.c
+++ src/wiki.c
@@ -122,20 +122,35 @@
122122
static int is_sandbox(const char *zPagename){
123123
return fossil_stricmp(zPagename,"sandbox")==0 ||
124124
fossil_stricmp(zPagename,"sand box")==0;
125125
}
126126
127
+/*
128
+** Formal, common and short names for the various wiki styles.
129
+*/
130
+static const char *const azStyles[] = {
131
+ "text/x-fossil-wiki", "Fossil Wiki", "wiki",
132
+ "text/x-markdown", "Markdown", "markdown",
133
+ "text/plain", "Plain Text", "plain"
134
+};
135
+
127136
/*
128137
** Only allow certain mimetypes through.
129138
** All others become "text/x-fossil-wiki"
130139
*/
131140
const char *wiki_filter_mimetypes(const char *zMimetype){
132
- if( zMimetype!=0 &&
133
- ( fossil_strcmp(zMimetype, "text/x-markdown")==0
134
- || fossil_strcmp(zMimetype, "text/plain")==0 )
135
- ){
136
- return zMimetype;
141
+ if( zMimetype!=0 ){
142
+ int i;
143
+ for(i=0; i<sizeof(azStyles)/sizeof(azStyles[0]); i+=3){
144
+ if( fossil_strcmp(zMimetype,azStyles[i+2])==0 ){
145
+ return azStyles[i];
146
+ }
147
+ }
148
+ if( fossil_strcmp(zMimetype, "text/x-markdown")==0
149
+ || fossil_strcmp(zMimetype, "text/plain")==0 ){
150
+ return zMimetype;
151
+ }
137152
}
138153
return "text/x-fossil-wiki";
139154
}
140155
141156
/*
@@ -412,27 +427,18 @@
412427
db_multi_exec("INSERT OR IGNORE INTO unsent VALUES(%d)", nrid);
413428
db_multi_exec("INSERT OR IGNORE INTO unclustered VALUES(%d);", nrid);
414429
manifest_crosslink(nrid, pWiki, MC_NONE);
415430
}
416431
417
-/*
418
-** Formal names and common names for the various wiki styles.
419
-*/
420
-static const char *const azStyles[] = {
421
- "text/x-fossil-wiki", "Fossil Wiki",
422
- "text/x-markdown", "Markdown",
423
- "text/plain", "Plain Text"
424
-};
425
-
426432
/*
427433
** Output a selection box from which the user can select the
428434
** wiki mimetype.
429435
*/
430436
void mimetype_option_menu(const char *zMimetype){
431437
unsigned i;
432438
@ <select name="mimetype" size="1">
433
- for(i=0; i<sizeof(azStyles)/sizeof(azStyles[0]); i+=2){
439
+ for(i=0; i<sizeof(azStyles)/sizeof(azStyles[0]); i+=3){
434440
if( fossil_strcmp(zMimetype,azStyles[i])==0 ){
435441
@ <option value="%s(azStyles[i])" selected>%s(azStyles[i+1])</option>
436442
}else{
437443
@ <option value="%s(azStyles[i])">%s(azStyles[i+1])</option>
438444
}
@@ -1068,47 +1074,26 @@
10681074
style_footer();
10691075
}
10701076
10711077
/*
10721078
** Add a new wiki page to the repository. The page name is
1073
-** given by the zPageName parameter. isNew must be true to create
1074
-** a new page. If no previous page with the name zPageName exists
1075
-** and isNew is false, then this routine throws an error.
1079
+** given by the zPageName parameter. rid must be zero to create
1080
+** a new page otherwise the page identified by rid is updated.
10761081
**
10771082
** The content of the new page is given by the blob pContent.
10781083
**
10791084
** zMimeType specifies the N-card for the wiki page. If it is 0,
10801085
** empty, or "text/x-fossil-wiki" (the default format) then it is
10811086
** ignored.
10821087
*/
1083
-int wiki_cmd_commit(const char *zPageName, int isNew, Blob *pContent,
1088
+int wiki_cmd_commit(const char *zPageName, int rid, Blob *pContent,
10841089
const char *zMimeType, int localUser){
10851090
Blob wiki; /* Wiki page content */
10861091
Blob cksum; /* wiki checksum */
1087
- int rid; /* artifact ID of parent page */
10881092
char *zDate; /* timestamp */
10891093
char *zUuid; /* uuid for rid */
10901094
1091
- rid = db_int(0,
1092
- "SELECT x.rid FROM tag t, tagxref x"
1093
- " WHERE x.tagid=t.tagid AND t.tagname='wiki-%q'"
1094
- " ORDER BY x.mtime DESC LIMIT 1",
1095
- zPageName
1096
- );
1097
- if( rid==0 && !isNew ){
1098
-#ifdef FOSSIL_ENABLE_JSON
1099
- g.json.resultCode = FSL_JSON_E_RESOURCE_NOT_FOUND;
1100
-#endif
1101
- fossil_fatal("no such wiki page: %s", zPageName);
1102
- }
1103
- if( rid!=0 && isNew ){
1104
-#ifdef FOSSIL_ENABLE_JSON
1105
- g.json.resultCode = FSL_JSON_E_RESOURCE_ALREADY_EXISTS;
1106
-#endif
1107
- fossil_fatal("wiki page %s already exists", zPageName);
1108
- }
1109
-
11101095
blob_zero(&wiki);
11111096
zDate = date_in_standard_format("now");
11121097
blob_appendf(&wiki, "D %s\n", zDate);
11131098
free(zDate);
11141099
blob_appendf(&wiki, "L %F\n", zPageName );
@@ -1133,47 +1118,112 @@
11331118
db_begin_transaction();
11341119
wiki_put(&wiki, 0, wiki_need_moderation(localUser));
11351120
db_end_transaction(0);
11361121
return 1;
11371122
}
1123
+
1124
+/*
1125
+** Determine the rid for a tech note given either its id or its
1126
+** timestamp. Returns 0 if there is no such item and -1 if the details
1127
+** are ambiguous and could refer to multiple items.
1128
+*/
1129
+int wiki_technote_to_rid(const char *zETime) {
1130
+ int rid=0; /* Artifact ID of the tech note */
1131
+ int nETime = strlen(zETime);
1132
+ Stmt q;
1133
+ if( nETime>=4 && nETime<=UUID_SIZE && validate16(zETime, nETime) ){
1134
+ char zUuid[UUID_SIZE+1];
1135
+ memcpy(zUuid, zETime, nETime+1);
1136
+ canonical16(zUuid, nETime);
1137
+ db_prepare(&q,
1138
+ "SELECT e.objid"
1139
+ " FROM event e, tag t"
1140
+ " WHERE e.type='e' AND e.tagid IS NOT NULL AND t.tagid=e.tagid"
1141
+ " AND t.tagname GLOB 'event-%q*'",
1142
+ zUuid
1143
+ );
1144
+ if( db_step(&q)==SQLITE_ROW ){
1145
+ rid = db_column_int(&q, 0);
1146
+ if( db_step(&q)==SQLITE_ROW ) rid = -1;
1147
+ }
1148
+ db_finalize(&q);
1149
+ }
1150
+ if (!rid) {
1151
+ if (strlen(zETime)>4) {
1152
+ rid = db_int(0, "SELECT objid"
1153
+ " FROM event"
1154
+ " WHERE datetime(mtime)=datetime('%q')"
1155
+ " AND type='e'"
1156
+ " AND tagid IS NOT NULL"
1157
+ " ORDER BY objid DESC LIMIT 1",
1158
+ zETime);
1159
+ }
1160
+ }
1161
+ return rid;
1162
+}
11381163
11391164
/*
11401165
** COMMAND: wiki*
11411166
**
11421167
** Usage: %fossil wiki (export|create|commit|list) WikiName
11431168
**
11441169
** Run various subcommands to work with wiki entries or tech notes.
11451170
**
1146
-** %fossil wiki export ?PAGENAME? ?FILE? [-t|--technote DATETIME ]
1171
+** %fossil wiki export PAGENAME ?FILE?
1172
+** %fossil wiki export ?FILE? -t|--technote DATETIME|TECHNOTE-ID
11471173
**
1148
-** Sends the latest version of either the PAGENAME wiki entry
1149
-** or the DATETIME tech note to the given file or standard
1150
-** output. One of PAGENAME or DATETIME must be specified.
1174
+** Sends the latest version of either a wiki page or of a tech note
1175
+** to the given file or standard output.
1176
+** If PAGENAME is provided, the wiki page will be output. For
1177
+** a tech note either DATETIME or TECHNOTE-ID must be specified. If
1178
+** DATETIME is used, the most recently modified tech note with that
1179
+** DATETIME will be sent.
11511180
**
11521181
** %fossil wiki (create|commit) PAGENAME ?FILE? ?OPTIONS?
11531182
**
11541183
** Create a new or commit changes to an existing wiki page or
1155
-** technote from FILE or from standard input.
1184
+** technote from FILE or from standard input. PAGENAME is the
1185
+** name of the wiki entry or the timeline comment of the
1186
+** technote.
11561187
**
11571188
** Options:
1158
-** -M|--mimetype TEXT-FORMAT The mimetype of the update defaulting
1159
-** to the type used by the previous version
1160
-** of the page or text/x-fossil-wiki.
1161
-** -t|--technote DATETIME Specifies the timestamp of the technote
1162
-** to be created or updated.
1189
+** -M|--mimetype TEXT-FORMAT The mime type of the update.
1190
+** Defaults to the type used by
1191
+** the previous version of the
1192
+** page, or text/x-fossil-wiki.
1193
+** Valid values are: text/x-fossil-wiki,
1194
+** text/markdown and text/plain. fossil,
1195
+** markdown or plain can be specified as
1196
+** synonyms of these values.
1197
+** -t|--technote DATETIME Specifies the timestamp of
1198
+** the technote to be created or
1199
+** updated. When updating a tech note
1200
+** the most recently modified tech note
1201
+** with the specified timestamp will be
1202
+** updated.
1203
+** -t|--technote TECHNOTE-ID Specifies the technote to be
1204
+** updated by its technote id.
11631205
** --technote-tags TAGS The set of tags for a technote.
1164
-** --technote-bgcolor COLOR The color used for the technote on the
1165
-** timeline.
1206
+** --technote-bgcolor COLOR The color used for the technote
1207
+** on the timeline.
11661208
**
1167
-** %fossil wiki list ?--technote?
1168
-** %fossil wiki ls ?--technote?
1209
+** %fossil wiki list ?OPTIONS?
1210
+** %fossil wiki ls ?OPTIONS?
11691211
**
11701212
** Lists all wiki entries, one per line, ordered
1171
-** case-insensitively by name. The --technote flag
1172
-** specifies that technotes will be listed instead of
1173
-** the wiki entries, which will be listed in order
1174
-** timestamp.
1213
+** case-insensitively by name.
1214
+**
1215
+** Options:
1216
+** -t|--technote Technotes will be listed instead of
1217
+** pages. The technotes will be in order
1218
+** of timestamp with the most recent
1219
+** first.
1220
+** -s|--show-technote-ids The id of the tech note will be listed
1221
+** along side the timestamp. The tech note
1222
+** id will be the first word on each line.
1223
+** This option only applies if the
1224
+** --technote option is also specified.
11751225
**
11761226
*/
11771227
void wiki_cmd(void){
11781228
int n;
11791229
db_find_and_open_repository(0, 0);
@@ -1213,22 +1263,21 @@
12131263
fossil_fatal("wiki page [%s] not found",zPageName);
12141264
}
12151265
zFile = (g.argc==4) ? "-" : g.argv[4];
12161266
}else{
12171267
if( (g.argc!=3) && (g.argc!=4) ){
1218
- usage("export ?FILE? --technote DATETIME");
1268
+ usage("export ?FILE? --technote DATETIME|TECHNOTE-ID");
12191269
}
1220
- rid = db_int(0, "SELECT objid FROM event"
1221
- " WHERE datetime(mtime)=datetime('%q') AND type='e'"
1222
- " ORDER BY mtime DESC LIMIT 1",
1223
- zETime
1224
- );
1270
+ rid = wiki_technote_to_rid(zETime);
1271
+ if (rid == -1) {
1272
+ fossil_fatal("ambiguous tech note id: %s", zETime);
1273
+ }
12251274
if( (pWiki = manifest_get(rid, CFTYPE_EVENT, 0))!=0 ){
12261275
zBody = pWiki->zWiki;
12271276
}
12281277
if( zBody==0 ){
1229
- fossil_fatal("technote not found");
1278
+ fossil_fatal("technote [%s] not found",zETime);
12301279
}
12311280
zFile = (g.argc==3) ? "-" : g.argv[3];
12321281
}
12331282
for(i=strlen(zBody); i>0 && fossil_isspace(zBody[i-1]); i--){}
12341283
zBody[i] = 0;
@@ -1270,43 +1319,58 @@
12701319
if(rid>0 && (pWiki = manifest_get(rid, CFTYPE_WIKI, 0))!=0
12711320
&& (pWiki->zMimetype && *pWiki->zMimetype)){
12721321
zMimeType = pWiki->zMimetype;
12731322
}
12741323
}else{
1275
- rid = db_int(0, "SELECT objid FROM event"
1276
- " WHERE datetime(mtime)=datetime('%q') AND type='e'"
1277
- " ORDER BY mtime DESC LIMIT 1",
1278
- zPageName
1279
- );
1324
+ rid = wiki_technote_to_rid(zETime);
12801325
if(rid>0 && (pWiki = manifest_get(rid, CFTYPE_EVENT, 0))!=0
12811326
&& (pWiki->zMimetype && *pWiki->zMimetype)){
12821327
zMimeType = pWiki->zMimetype;
12831328
}
12841329
}
1330
+ }else{
1331
+ zMimeType = wiki_filter_mimetypes(zMimeType);
1332
+ }
1333
+ if( g.argv[2][1]=='r' && rid>0 ){
1334
+ if ( !zETime ){
1335
+ fossil_fatal("wiki page %s already exists", zPageName);
1336
+ }else{
1337
+ /* Creating a tech note with same timestamp is permitted
1338
+ and should create a new tech note */
1339
+ rid = 0;
1340
+ }
1341
+ }else if( g.argv[2][1]=='o' && rid == 0 ){
1342
+ if ( !zETime ){
1343
+ fossil_fatal("no such wiki page: %s", zPageName);
1344
+ }else{
1345
+ fossil_fatal("no such tech note: %s", zETime);
1346
+ }
12851347
}
1348
+
12861349
if( !zETime ){
1350
+ wiki_cmd_commit(zPageName, rid, &content, zMimeType, 1);
12871351
if( g.argv[2][1]=='r' ){
1288
- wiki_cmd_commit(zPageName, 1, &content, zMimeType, 1);
12891352
fossil_print("Created new wiki page %s.\n", zPageName);
12901353
}else{
1291
- wiki_cmd_commit(zPageName, 0, &content, zMimeType, 1);
12921354
fossil_print("Updated wiki page %s.\n", zPageName);
12931355
}
12941356
}else{
1295
- char *zMETime; /* Normalized, mutable version of zETime */
1296
- zMETime = db_text(0, "SELECT coalesce(datetime(%Q),datetime('now'))",
1297
- zETime);
1298
- if( g.argv[2][1]=='r' ){
1299
- event_cmd_commit(zMETime, 1, &content, zMimeType, zPageName,
1300
- zTags, zClr);
1301
- fossil_print("Created new tech note %s.\n", zMETime);
1302
- }else{
1303
- event_cmd_commit(zMETime, 0, &content, zMimeType, zPageName,
1304
- zTags, zClr);
1305
- fossil_print("Updated tech note %s.\n", zMETime);
1306
- }
1307
- free(zMETime);
1357
+ if( rid != -1 ){
1358
+ char *zMETime; /* Normalized, mutable version of zETime */
1359
+ zMETime = db_text(0, "SELECT coalesce(datetime(%Q),datetime('now'))",
1360
+ zETime);
1361
+ event_cmd_commit(zMETime, rid, &content, zMimeType, zPageName,
1362
+ zTags, zClr);
1363
+ if( g.argv[2][1]=='r' ){
1364
+ fossil_print("Created new tech note %s.\n", zMETime);
1365
+ }else{
1366
+ fossil_print("Updated tech note %s.\n", zMETime);
1367
+ }
1368
+ free(zMETime);
1369
+ }else{
1370
+ fossil_fatal("ambiguous tech note id: %s", zETime);
1371
+ }
13081372
}
13091373
manifest_destroy(pWiki);
13101374
blob_reset(&content);
13111375
}else if( strncmp(g.argv[2],"delete",n)==0 ){
13121376
if( g.argc!=5 ){
@@ -1314,23 +1378,35 @@
13141378
}
13151379
fossil_fatal("delete not yet implemented.");
13161380
}else if(( strncmp(g.argv[2],"list",n)==0 )
13171381
|| ( strncmp(g.argv[2],"ls",n)==0 )){
13181382
Stmt q;
1383
+ int showIds = 0;
1384
+
13191385
if ( !find_option("technote","t",0) ){
13201386
db_prepare(&q,
13211387
"SELECT substr(tagname, 6) FROM tag WHERE tagname GLOB 'wiki-*'"
13221388
" ORDER BY lower(tagname) /*sort*/"
13231389
);
13241390
}else{
1391
+ showIds = find_option("show-technote-ids","s",0)!=0;
13251392
db_prepare(&q,
1326
- "SELECT datetime(mtime) FROM event WHERE type='e'"
1327
- " ORDER BY mtime /*sort*/"
1393
+ "SELECT datetime(e.mtime), substr(t.tagname,7)"
1394
+ " FROM event e, tag t"
1395
+ " WHERE e.type='e'"
1396
+ " AND e.tagid IS NOT NULL"
1397
+ " AND t.tagid=e.tagid"
1398
+ " ORDER BY e.mtime DESC /*sort*/"
13281399
);
13291400
}
1401
+
13301402
while( db_step(&q)==SQLITE_ROW ){
13311403
const char *zName = db_column_text(&q, 0);
1404
+ if (showIds) {
1405
+ const char *zUuid = db_column_text(&q, 1);
1406
+ fossil_print("%s ",zUuid);
1407
+ }
13321408
fossil_print( "%s\n",zName);
13331409
}
13341410
db_finalize(&q);
13351411
}else{
13361412
goto wiki_cmd_usage;
13371413
13381414
ADDED test/wiki.test
--- src/wiki.c
+++ src/wiki.c
@@ -122,20 +122,35 @@
122 static int is_sandbox(const char *zPagename){
123 return fossil_stricmp(zPagename,"sandbox")==0 ||
124 fossil_stricmp(zPagename,"sand box")==0;
125 }
126
 
 
 
 
 
 
 
 
 
127 /*
128 ** Only allow certain mimetypes through.
129 ** All others become "text/x-fossil-wiki"
130 */
131 const char *wiki_filter_mimetypes(const char *zMimetype){
132 if( zMimetype!=0 &&
133 ( fossil_strcmp(zMimetype, "text/x-markdown")==0
134 || fossil_strcmp(zMimetype, "text/plain")==0 )
135 ){
136 return zMimetype;
 
 
 
 
 
 
137 }
138 return "text/x-fossil-wiki";
139 }
140
141 /*
@@ -412,27 +427,18 @@
412 db_multi_exec("INSERT OR IGNORE INTO unsent VALUES(%d)", nrid);
413 db_multi_exec("INSERT OR IGNORE INTO unclustered VALUES(%d);", nrid);
414 manifest_crosslink(nrid, pWiki, MC_NONE);
415 }
416
417 /*
418 ** Formal names and common names for the various wiki styles.
419 */
420 static const char *const azStyles[] = {
421 "text/x-fossil-wiki", "Fossil Wiki",
422 "text/x-markdown", "Markdown",
423 "text/plain", "Plain Text"
424 };
425
426 /*
427 ** Output a selection box from which the user can select the
428 ** wiki mimetype.
429 */
430 void mimetype_option_menu(const char *zMimetype){
431 unsigned i;
432 @ <select name="mimetype" size="1">
433 for(i=0; i<sizeof(azStyles)/sizeof(azStyles[0]); i+=2){
434 if( fossil_strcmp(zMimetype,azStyles[i])==0 ){
435 @ <option value="%s(azStyles[i])" selected>%s(azStyles[i+1])</option>
436 }else{
437 @ <option value="%s(azStyles[i])">%s(azStyles[i+1])</option>
438 }
@@ -1068,47 +1074,26 @@
1068 style_footer();
1069 }
1070
1071 /*
1072 ** Add a new wiki page to the repository. The page name is
1073 ** given by the zPageName parameter. isNew must be true to create
1074 ** a new page. If no previous page with the name zPageName exists
1075 ** and isNew is false, then this routine throws an error.
1076 **
1077 ** The content of the new page is given by the blob pContent.
1078 **
1079 ** zMimeType specifies the N-card for the wiki page. If it is 0,
1080 ** empty, or "text/x-fossil-wiki" (the default format) then it is
1081 ** ignored.
1082 */
1083 int wiki_cmd_commit(const char *zPageName, int isNew, Blob *pContent,
1084 const char *zMimeType, int localUser){
1085 Blob wiki; /* Wiki page content */
1086 Blob cksum; /* wiki checksum */
1087 int rid; /* artifact ID of parent page */
1088 char *zDate; /* timestamp */
1089 char *zUuid; /* uuid for rid */
1090
1091 rid = db_int(0,
1092 "SELECT x.rid FROM tag t, tagxref x"
1093 " WHERE x.tagid=t.tagid AND t.tagname='wiki-%q'"
1094 " ORDER BY x.mtime DESC LIMIT 1",
1095 zPageName
1096 );
1097 if( rid==0 && !isNew ){
1098 #ifdef FOSSIL_ENABLE_JSON
1099 g.json.resultCode = FSL_JSON_E_RESOURCE_NOT_FOUND;
1100 #endif
1101 fossil_fatal("no such wiki page: %s", zPageName);
1102 }
1103 if( rid!=0 && isNew ){
1104 #ifdef FOSSIL_ENABLE_JSON
1105 g.json.resultCode = FSL_JSON_E_RESOURCE_ALREADY_EXISTS;
1106 #endif
1107 fossil_fatal("wiki page %s already exists", zPageName);
1108 }
1109
1110 blob_zero(&wiki);
1111 zDate = date_in_standard_format("now");
1112 blob_appendf(&wiki, "D %s\n", zDate);
1113 free(zDate);
1114 blob_appendf(&wiki, "L %F\n", zPageName );
@@ -1133,47 +1118,112 @@
1133 db_begin_transaction();
1134 wiki_put(&wiki, 0, wiki_need_moderation(localUser));
1135 db_end_transaction(0);
1136 return 1;
1137 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1138
1139 /*
1140 ** COMMAND: wiki*
1141 **
1142 ** Usage: %fossil wiki (export|create|commit|list) WikiName
1143 **
1144 ** Run various subcommands to work with wiki entries or tech notes.
1145 **
1146 ** %fossil wiki export ?PAGENAME? ?FILE? [-t|--technote DATETIME ]
 
1147 **
1148 ** Sends the latest version of either the PAGENAME wiki entry
1149 ** or the DATETIME tech note to the given file or standard
1150 ** output. One of PAGENAME or DATETIME must be specified.
 
 
 
1151 **
1152 ** %fossil wiki (create|commit) PAGENAME ?FILE? ?OPTIONS?
1153 **
1154 ** Create a new or commit changes to an existing wiki page or
1155 ** technote from FILE or from standard input.
 
 
1156 **
1157 ** Options:
1158 ** -M|--mimetype TEXT-FORMAT The mimetype of the update defaulting
1159 ** to the type used by the previous version
1160 ** of the page or text/x-fossil-wiki.
1161 ** -t|--technote DATETIME Specifies the timestamp of the technote
1162 ** to be created or updated.
 
 
 
 
 
 
 
 
 
 
 
1163 ** --technote-tags TAGS The set of tags for a technote.
1164 ** --technote-bgcolor COLOR The color used for the technote on the
1165 ** timeline.
1166 **
1167 ** %fossil wiki list ?--technote?
1168 ** %fossil wiki ls ?--technote?
1169 **
1170 ** Lists all wiki entries, one per line, ordered
1171 ** case-insensitively by name. The --technote flag
1172 ** specifies that technotes will be listed instead of
1173 ** the wiki entries, which will be listed in order
1174 ** timestamp.
 
 
 
 
 
 
 
 
1175 **
1176 */
1177 void wiki_cmd(void){
1178 int n;
1179 db_find_and_open_repository(0, 0);
@@ -1213,22 +1263,21 @@
1213 fossil_fatal("wiki page [%s] not found",zPageName);
1214 }
1215 zFile = (g.argc==4) ? "-" : g.argv[4];
1216 }else{
1217 if( (g.argc!=3) && (g.argc!=4) ){
1218 usage("export ?FILE? --technote DATETIME");
1219 }
1220 rid = db_int(0, "SELECT objid FROM event"
1221 " WHERE datetime(mtime)=datetime('%q') AND type='e'"
1222 " ORDER BY mtime DESC LIMIT 1",
1223 zETime
1224 );
1225 if( (pWiki = manifest_get(rid, CFTYPE_EVENT, 0))!=0 ){
1226 zBody = pWiki->zWiki;
1227 }
1228 if( zBody==0 ){
1229 fossil_fatal("technote not found");
1230 }
1231 zFile = (g.argc==3) ? "-" : g.argv[3];
1232 }
1233 for(i=strlen(zBody); i>0 && fossil_isspace(zBody[i-1]); i--){}
1234 zBody[i] = 0;
@@ -1270,43 +1319,58 @@
1270 if(rid>0 && (pWiki = manifest_get(rid, CFTYPE_WIKI, 0))!=0
1271 && (pWiki->zMimetype && *pWiki->zMimetype)){
1272 zMimeType = pWiki->zMimetype;
1273 }
1274 }else{
1275 rid = db_int(0, "SELECT objid FROM event"
1276 " WHERE datetime(mtime)=datetime('%q') AND type='e'"
1277 " ORDER BY mtime DESC LIMIT 1",
1278 zPageName
1279 );
1280 if(rid>0 && (pWiki = manifest_get(rid, CFTYPE_EVENT, 0))!=0
1281 && (pWiki->zMimetype && *pWiki->zMimetype)){
1282 zMimeType = pWiki->zMimetype;
1283 }
1284 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1285 }
 
1286 if( !zETime ){
 
1287 if( g.argv[2][1]=='r' ){
1288 wiki_cmd_commit(zPageName, 1, &content, zMimeType, 1);
1289 fossil_print("Created new wiki page %s.\n", zPageName);
1290 }else{
1291 wiki_cmd_commit(zPageName, 0, &content, zMimeType, 1);
1292 fossil_print("Updated wiki page %s.\n", zPageName);
1293 }
1294 }else{
1295 char *zMETime; /* Normalized, mutable version of zETime */
1296 zMETime = db_text(0, "SELECT coalesce(datetime(%Q),datetime('now'))",
1297 zETime);
1298 if( g.argv[2][1]=='r' ){
1299 event_cmd_commit(zMETime, 1, &content, zMimeType, zPageName,
1300 zTags, zClr);
1301 fossil_print("Created new tech note %s.\n", zMETime);
1302 }else{
1303 event_cmd_commit(zMETime, 0, &content, zMimeType, zPageName,
1304 zTags, zClr);
1305 fossil_print("Updated tech note %s.\n", zMETime);
1306 }
1307 free(zMETime);
 
 
1308 }
1309 manifest_destroy(pWiki);
1310 blob_reset(&content);
1311 }else if( strncmp(g.argv[2],"delete",n)==0 ){
1312 if( g.argc!=5 ){
@@ -1314,23 +1378,35 @@
1314 }
1315 fossil_fatal("delete not yet implemented.");
1316 }else if(( strncmp(g.argv[2],"list",n)==0 )
1317 || ( strncmp(g.argv[2],"ls",n)==0 )){
1318 Stmt q;
 
 
1319 if ( !find_option("technote","t",0) ){
1320 db_prepare(&q,
1321 "SELECT substr(tagname, 6) FROM tag WHERE tagname GLOB 'wiki-*'"
1322 " ORDER BY lower(tagname) /*sort*/"
1323 );
1324 }else{
 
1325 db_prepare(&q,
1326 "SELECT datetime(mtime) FROM event WHERE type='e'"
1327 " ORDER BY mtime /*sort*/"
 
 
 
 
1328 );
1329 }
 
1330 while( db_step(&q)==SQLITE_ROW ){
1331 const char *zName = db_column_text(&q, 0);
 
 
 
 
1332 fossil_print( "%s\n",zName);
1333 }
1334 db_finalize(&q);
1335 }else{
1336 goto wiki_cmd_usage;
1337
1338 DDED test/wiki.test
--- src/wiki.c
+++ src/wiki.c
@@ -122,20 +122,35 @@
122 static int is_sandbox(const char *zPagename){
123 return fossil_stricmp(zPagename,"sandbox")==0 ||
124 fossil_stricmp(zPagename,"sand box")==0;
125 }
126
127 /*
128 ** Formal, common and short names for the various wiki styles.
129 */
130 static const char *const azStyles[] = {
131 "text/x-fossil-wiki", "Fossil Wiki", "wiki",
132 "text/x-markdown", "Markdown", "markdown",
133 "text/plain", "Plain Text", "plain"
134 };
135
136 /*
137 ** Only allow certain mimetypes through.
138 ** All others become "text/x-fossil-wiki"
139 */
140 const char *wiki_filter_mimetypes(const char *zMimetype){
141 if( zMimetype!=0 ){
142 int i;
143 for(i=0; i<sizeof(azStyles)/sizeof(azStyles[0]); i+=3){
144 if( fossil_strcmp(zMimetype,azStyles[i+2])==0 ){
145 return azStyles[i];
146 }
147 }
148 if( fossil_strcmp(zMimetype, "text/x-markdown")==0
149 || fossil_strcmp(zMimetype, "text/plain")==0 ){
150 return zMimetype;
151 }
152 }
153 return "text/x-fossil-wiki";
154 }
155
156 /*
@@ -412,27 +427,18 @@
427 db_multi_exec("INSERT OR IGNORE INTO unsent VALUES(%d)", nrid);
428 db_multi_exec("INSERT OR IGNORE INTO unclustered VALUES(%d);", nrid);
429 manifest_crosslink(nrid, pWiki, MC_NONE);
430 }
431
 
 
 
 
 
 
 
 
 
432 /*
433 ** Output a selection box from which the user can select the
434 ** wiki mimetype.
435 */
436 void mimetype_option_menu(const char *zMimetype){
437 unsigned i;
438 @ <select name="mimetype" size="1">
439 for(i=0; i<sizeof(azStyles)/sizeof(azStyles[0]); i+=3){
440 if( fossil_strcmp(zMimetype,azStyles[i])==0 ){
441 @ <option value="%s(azStyles[i])" selected>%s(azStyles[i+1])</option>
442 }else{
443 @ <option value="%s(azStyles[i])">%s(azStyles[i+1])</option>
444 }
@@ -1068,47 +1074,26 @@
1074 style_footer();
1075 }
1076
1077 /*
1078 ** Add a new wiki page to the repository. The page name is
1079 ** given by the zPageName parameter. rid must be zero to create
1080 ** a new page otherwise the page identified by rid is updated.
 
1081 **
1082 ** The content of the new page is given by the blob pContent.
1083 **
1084 ** zMimeType specifies the N-card for the wiki page. If it is 0,
1085 ** empty, or "text/x-fossil-wiki" (the default format) then it is
1086 ** ignored.
1087 */
1088 int wiki_cmd_commit(const char *zPageName, int rid, Blob *pContent,
1089 const char *zMimeType, int localUser){
1090 Blob wiki; /* Wiki page content */
1091 Blob cksum; /* wiki checksum */
 
1092 char *zDate; /* timestamp */
1093 char *zUuid; /* uuid for rid */
1094
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1095 blob_zero(&wiki);
1096 zDate = date_in_standard_format("now");
1097 blob_appendf(&wiki, "D %s\n", zDate);
1098 free(zDate);
1099 blob_appendf(&wiki, "L %F\n", zPageName );
@@ -1133,47 +1118,112 @@
1118 db_begin_transaction();
1119 wiki_put(&wiki, 0, wiki_need_moderation(localUser));
1120 db_end_transaction(0);
1121 return 1;
1122 }
1123
1124 /*
1125 ** Determine the rid for a tech note given either its id or its
1126 ** timestamp. Returns 0 if there is no such item and -1 if the details
1127 ** are ambiguous and could refer to multiple items.
1128 */
1129 int wiki_technote_to_rid(const char *zETime) {
1130 int rid=0; /* Artifact ID of the tech note */
1131 int nETime = strlen(zETime);
1132 Stmt q;
1133 if( nETime>=4 && nETime<=UUID_SIZE && validate16(zETime, nETime) ){
1134 char zUuid[UUID_SIZE+1];
1135 memcpy(zUuid, zETime, nETime+1);
1136 canonical16(zUuid, nETime);
1137 db_prepare(&q,
1138 "SELECT e.objid"
1139 " FROM event e, tag t"
1140 " WHERE e.type='e' AND e.tagid IS NOT NULL AND t.tagid=e.tagid"
1141 " AND t.tagname GLOB 'event-%q*'",
1142 zUuid
1143 );
1144 if( db_step(&q)==SQLITE_ROW ){
1145 rid = db_column_int(&q, 0);
1146 if( db_step(&q)==SQLITE_ROW ) rid = -1;
1147 }
1148 db_finalize(&q);
1149 }
1150 if (!rid) {
1151 if (strlen(zETime)>4) {
1152 rid = db_int(0, "SELECT objid"
1153 " FROM event"
1154 " WHERE datetime(mtime)=datetime('%q')"
1155 " AND type='e'"
1156 " AND tagid IS NOT NULL"
1157 " ORDER BY objid DESC LIMIT 1",
1158 zETime);
1159 }
1160 }
1161 return rid;
1162 }
1163
1164 /*
1165 ** COMMAND: wiki*
1166 **
1167 ** Usage: %fossil wiki (export|create|commit|list) WikiName
1168 **
1169 ** Run various subcommands to work with wiki entries or tech notes.
1170 **
1171 ** %fossil wiki export PAGENAME ?FILE?
1172 ** %fossil wiki export ?FILE? -t|--technote DATETIME|TECHNOTE-ID
1173 **
1174 ** Sends the latest version of either a wiki page or of a tech note
1175 ** to the given file or standard output.
1176 ** If PAGENAME is provided, the wiki page will be output. For
1177 ** a tech note either DATETIME or TECHNOTE-ID must be specified. If
1178 ** DATETIME is used, the most recently modified tech note with that
1179 ** DATETIME will be sent.
1180 **
1181 ** %fossil wiki (create|commit) PAGENAME ?FILE? ?OPTIONS?
1182 **
1183 ** Create a new or commit changes to an existing wiki page or
1184 ** technote from FILE or from standard input. PAGENAME is the
1185 ** name of the wiki entry or the timeline comment of the
1186 ** technote.
1187 **
1188 ** Options:
1189 ** -M|--mimetype TEXT-FORMAT The mime type of the update.
1190 ** Defaults to the type used by
1191 ** the previous version of the
1192 ** page, or text/x-fossil-wiki.
1193 ** Valid values are: text/x-fossil-wiki,
1194 ** text/markdown and text/plain. fossil,
1195 ** markdown or plain can be specified as
1196 ** synonyms of these values.
1197 ** -t|--technote DATETIME Specifies the timestamp of
1198 ** the technote to be created or
1199 ** updated. When updating a tech note
1200 ** the most recently modified tech note
1201 ** with the specified timestamp will be
1202 ** updated.
1203 ** -t|--technote TECHNOTE-ID Specifies the technote to be
1204 ** updated by its technote id.
1205 ** --technote-tags TAGS The set of tags for a technote.
1206 ** --technote-bgcolor COLOR The color used for the technote
1207 ** on the timeline.
1208 **
1209 ** %fossil wiki list ?OPTIONS?
1210 ** %fossil wiki ls ?OPTIONS?
1211 **
1212 ** Lists all wiki entries, one per line, ordered
1213 ** case-insensitively by name.
1214 **
1215 ** Options:
1216 ** -t|--technote Technotes will be listed instead of
1217 ** pages. The technotes will be in order
1218 ** of timestamp with the most recent
1219 ** first.
1220 ** -s|--show-technote-ids The id of the tech note will be listed
1221 ** along side the timestamp. The tech note
1222 ** id will be the first word on each line.
1223 ** This option only applies if the
1224 ** --technote option is also specified.
1225 **
1226 */
1227 void wiki_cmd(void){
1228 int n;
1229 db_find_and_open_repository(0, 0);
@@ -1213,22 +1263,21 @@
1263 fossil_fatal("wiki page [%s] not found",zPageName);
1264 }
1265 zFile = (g.argc==4) ? "-" : g.argv[4];
1266 }else{
1267 if( (g.argc!=3) && (g.argc!=4) ){
1268 usage("export ?FILE? --technote DATETIME|TECHNOTE-ID");
1269 }
1270 rid = wiki_technote_to_rid(zETime);
1271 if (rid == -1) {
1272 fossil_fatal("ambiguous tech note id: %s", zETime);
1273 }
 
1274 if( (pWiki = manifest_get(rid, CFTYPE_EVENT, 0))!=0 ){
1275 zBody = pWiki->zWiki;
1276 }
1277 if( zBody==0 ){
1278 fossil_fatal("technote [%s] not found",zETime);
1279 }
1280 zFile = (g.argc==3) ? "-" : g.argv[3];
1281 }
1282 for(i=strlen(zBody); i>0 && fossil_isspace(zBody[i-1]); i--){}
1283 zBody[i] = 0;
@@ -1270,43 +1319,58 @@
1319 if(rid>0 && (pWiki = manifest_get(rid, CFTYPE_WIKI, 0))!=0
1320 && (pWiki->zMimetype && *pWiki->zMimetype)){
1321 zMimeType = pWiki->zMimetype;
1322 }
1323 }else{
1324 rid = wiki_technote_to_rid(zETime);
 
 
 
 
1325 if(rid>0 && (pWiki = manifest_get(rid, CFTYPE_EVENT, 0))!=0
1326 && (pWiki->zMimetype && *pWiki->zMimetype)){
1327 zMimeType = pWiki->zMimetype;
1328 }
1329 }
1330 }else{
1331 zMimeType = wiki_filter_mimetypes(zMimeType);
1332 }
1333 if( g.argv[2][1]=='r' && rid>0 ){
1334 if ( !zETime ){
1335 fossil_fatal("wiki page %s already exists", zPageName);
1336 }else{
1337 /* Creating a tech note with same timestamp is permitted
1338 and should create a new tech note */
1339 rid = 0;
1340 }
1341 }else if( g.argv[2][1]=='o' && rid == 0 ){
1342 if ( !zETime ){
1343 fossil_fatal("no such wiki page: %s", zPageName);
1344 }else{
1345 fossil_fatal("no such tech note: %s", zETime);
1346 }
1347 }
1348
1349 if( !zETime ){
1350 wiki_cmd_commit(zPageName, rid, &content, zMimeType, 1);
1351 if( g.argv[2][1]=='r' ){
 
1352 fossil_print("Created new wiki page %s.\n", zPageName);
1353 }else{
 
1354 fossil_print("Updated wiki page %s.\n", zPageName);
1355 }
1356 }else{
1357 if( rid != -1 ){
1358 char *zMETime; /* Normalized, mutable version of zETime */
1359 zMETime = db_text(0, "SELECT coalesce(datetime(%Q),datetime('now'))",
1360 zETime);
1361 event_cmd_commit(zMETime, rid, &content, zMimeType, zPageName,
1362 zTags, zClr);
1363 if( g.argv[2][1]=='r' ){
1364 fossil_print("Created new tech note %s.\n", zMETime);
1365 }else{
1366 fossil_print("Updated tech note %s.\n", zMETime);
1367 }
1368 free(zMETime);
1369 }else{
1370 fossil_fatal("ambiguous tech note id: %s", zETime);
1371 }
1372 }
1373 manifest_destroy(pWiki);
1374 blob_reset(&content);
1375 }else if( strncmp(g.argv[2],"delete",n)==0 ){
1376 if( g.argc!=5 ){
@@ -1314,23 +1378,35 @@
1378 }
1379 fossil_fatal("delete not yet implemented.");
1380 }else if(( strncmp(g.argv[2],"list",n)==0 )
1381 || ( strncmp(g.argv[2],"ls",n)==0 )){
1382 Stmt q;
1383 int showIds = 0;
1384
1385 if ( !find_option("technote","t",0) ){
1386 db_prepare(&q,
1387 "SELECT substr(tagname, 6) FROM tag WHERE tagname GLOB 'wiki-*'"
1388 " ORDER BY lower(tagname) /*sort*/"
1389 );
1390 }else{
1391 showIds = find_option("show-technote-ids","s",0)!=0;
1392 db_prepare(&q,
1393 "SELECT datetime(e.mtime), substr(t.tagname,7)"
1394 " FROM event e, tag t"
1395 " WHERE e.type='e'"
1396 " AND e.tagid IS NOT NULL"
1397 " AND t.tagid=e.tagid"
1398 " ORDER BY e.mtime DESC /*sort*/"
1399 );
1400 }
1401
1402 while( db_step(&q)==SQLITE_ROW ){
1403 const char *zName = db_column_text(&q, 0);
1404 if (showIds) {
1405 const char *zUuid = db_column_text(&q, 1);
1406 fossil_print("%s ",zUuid);
1407 }
1408 fossil_print( "%s\n",zName);
1409 }
1410 db_finalize(&q);
1411 }else{
1412 goto wiki_cmd_usage;
1413
1414 DDED test/wiki.test
--- a/test/wiki.test
+++ b/test/wiki.test
@@ -0,0 +1,334 @@
1
+#
2
+# Copyright (c) 2016 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
6
+# known as the "2-Clause License" or "FreeBSD License".)
7
+#
8
+# This program is distributed in the hope that it will be useful,
9
+# but without any warranty; without even the implied warranty of
10
+# merchantability or fitness for a particular purpose.
11
+#
12
+# Author contact information:
13
+# [email protected]
14
+# http://www.hwaci.com/drh/
15
+#
16
+############################################################################
17
+#
18
+# Test wiki and attachment comman (c) 2016 D. Richard Hipp
19
+#
20
+# This program is free software; you can redistribute it and/or
21
+# modify it under the terms of the Simplified BSD License (also
22
+# known as the "2buted in the hoperegsub -all { +\n} $x \n x
23
+ (c) 2016 D. Richard Hipp
24
+#
25
+# #
26
+# Copyright (c) 201 that it will be usefuy \n y
27
+ (c) 2016 D. Richard #
28
+# Coi.com
29
+# http://www.hwaci.com/drh/
30
+#
31
+############################################################################
32
+#
33
+# Test wiki and attachment command Support
34
+#
35
+
36
+test_setup
37
+
38
+# Disable backoffice for this test, otherwise its process lingers for some
39
+# time after the test has completed.
40
+# Perhaps, this should be done in test_setup and enabled explicitly only
41
+# when needed.
42
+fossil set backoffice-disable 1
43
+
44
+# Return true if two files are similar (i.e not only compress trailing spaces
45
+# from a lset CODE [regex regsub -all { +\n} $x \n x
46
+ No info link found [read_file $b]
47
+ regsub -all { +http << "GET /artifact/$info D. Richard Hip#
48
+# Copyright {a b} {
49
+ set x ""
50
+ if {[file exists $a]} {
51
+ set x [read_file $a]
52
+ regsub -all { +\n} $x \n x
53
+ regsub -all {\n$} $x {} x
54
+ }
55
+ set y ""
56
+ if {[file exists $b]} {
57
+ set y [read_file $b]
58
+ regsub -all { +\n} $y \n y
59
+ regsub -all {\n$} $y {} y
60
+ }
61
+ return [expr {$x==$y}]
62
+}
63
+
64
+# Return the mime type in the manifest for a given wiki page
65
+# Defaults to "error: some text" if the manifest can't be located and
66
+# "text/x-fossil-wiki" (the default mimetype for rendering)
67
+# if the N card is omitted in the manifest.
68
+# Note: Makes fossil calls, so $CODE and $RESULT will be corrupted
69
+proc get_mime_type {name} {
70
+ global CODE RESULT
71
+ fossil http << "GET /wiki?name=$name"
72
+ if {$CODE != 0} {
73
+ return "error: /wiki?name=$name $CODE $RESULT"
74
+ }
75
+ fossil whatis --type w $name
76
+ if {$CODE != 0} {
77
+ return "error: fossil whatis --type w $name $CODE $RESULT"
78
+ }
79
+ set CODE [regexp -line {^artifact:\s*([0-9a-f]+)$} $RESULT match info]
80
+ if {$CODE == 0} {
81
+ return "error: whatis returned no info for wiki page $name"
82
+ }
83
+ fossil artifact $info
84
+ if {$CODE != 0} {
85
+ return "error: fossil artifact $info $CODE $RESULT"
86
+ }
87
+ set CODE [regexp -line {^N (.*)$} $RESULT match mimetype]
88
+ if {$CODE == 0} {
89
+ return "text/x-fossil-wiki"
90
+ }
91
+ return $mimetype
92
+}
93
+
94
+
95
+###############################################################################
96
+# Initially there should be no wiki entries
97
+fossil wiki list
98
+test wiki-0 {[normalize_result] eq {}}
99
+
100
+###############################################################################
101
+# Adding an entry should add it to the wiki list
102
+write_file f1 "first wiki note"
103
+fossil wiki create tcltest f1
104
+test wiki-1 {$CODE == 0}
105
+fossil wiki list
106
+test wiki-2 {[normalize_result] eq {tcltest}}
107
+
108
+###############################################################################
109
+# Trying to add the same entry should fail
110
+fossil wiki create tcltest f1 -expectError
111
+test wiki-3 {$CODE != 0}
112
+
113
+###############################################################################
114
+# exporting the wiki page should give back similar text
115
+fossil wiki export tcltest a1
116
+test wiki-4 {[similar_file f1 a1]}
117
+
118
+###############################################################################
119
+# commiting a change to an existing page should replace the page on export
120
+write_file f2 "second version of the page"
121
+fossil wiki commit tcltest f2
122
+test wiki-5 {$CODE == 0}
123
+fossil wiki export tcltest a2
124
+test wiki-6 {[similar_file f2 a2]}
125
+
126
+###############################################################################
127
+# But we shouldn't be able to update non-existant pages
128
+fossil wiki commit doesntexist f1 -expectError
129
+test wiki-7 {$CODE != 0}
130
+
131
+###############################################################################
132
+# There shouldn't be any tech notes at this point
133
+fossil wiki list --technote
134
+test wiki-8 {[normalize_result] eq {}}
135
+
136
+###############################################################################
137
+# Creating a tech note with a specified timestamp should add a technote
138
+write_file f3 "A technote"
139
+f ossil wiki create technote f3 --technote {2016-01-01 12:34}
140
+test wiki-9 {$CODE == 0}
141
+fossil wiki list --technote
142
+test wiki-10 {[normalize_result] eq {2016-01-01 12:34:00}}
143
+fossil wiki list --technote --show-technote-ids
144
+set technotelist [split $RESULT "\n"]
145
+set veryfirsttechnoteid [lindex [split [lindex $technotelist 0]] 0]
146
+
147
+###############################################################################
148
+# exporting that technote should give back similar text
149
+fossil wiki export a3 --technote {2016-01-01 12:34:00}
150
+test wiki-11 {[similar_file f3 a3]}
151
+
152
+###############################################################################
153
+# Trying to add a technote with the same timestamp should succeed and create a
154
+# second tech note
155
+fossil wiki create 2ndnote f3 -technote {2016-01-01 12:34}
156
+test wiki-13 {$CODE == 0}
157
+fossil wiki list --technote
158
+set technotelist [split $RESULT "\n"]
159
+test wiki-13.1 {[llength $technotelist] == 2}
160
+
161
+###############################################################################
162
+# commiting a change to an existing technote should replace the page on export
163
+# (this should update th rt
164
+# (this should update the tech note from wiki-13 as that the most recently
165
+# updated one, that should also be the one exported by the export command)
166
+write_file f4 "technote 2nd variant"
167
+fossil wiki commit technote f4 --technote {2016-01-01 12:34}
168
+test wiki-14 {$CODE == 0}
169
+fossil wiki export a4 --technote {2016-01-01 12:34}
170
+test wiki-15 {[similar_file f4 a4]}
171
+# Also check that the tech note with the same timestamp, but modified less
172
+# recently still has its original text
173
+fossil wiki export a4.1 --technote $veryfirsttechnoteid
174
+test wiki-15.1 {[similar_file f3 a4.1]}
175
+
176
+###############################################################################
177
+# But we shouldn't be able to update non-existant pages
178
+fossil wiki commit doesntexist f1 -expectError
179
+test wiki-16 {$CODE != 0}
180
+
181
+###############################################################################
182
+# Check specifying tags for a technote is OK
183
+write_file f5 "technote with tags"
184
+fossil wiki create {tagged technote} f5 --technote {2016-01-02 12:34} --technote-tags {A B}
185
+test wiki-17 {$CODE == 0}
186
+write_file f5.1 "editted and tagged technote"
187
+fossil wiki commit {tagged technote} f5 --technote {2016-01-02 12:34} --t note {2016-01 -03 12:34} --technote-bgcolor blue
188
+test wiki-28 {$CODE == 0}
189
+
190
+###############################################################################
191
+# _file f7 "Different timestamps"
192
+fossil wiki create technotenow f7 --technote {2016-01-04 12:34:56+00:00}
193
+test wiki-29 {$CODE == 0}
194
+
195
+###############################################################################
196
+# Check a technote appears on the timeline
197
+write_file f8 "Contents of a 'unique' tech note"
198
+fossil wiki create {Unique technote} f8 --technote {2016-01-05 01:02:03}
199
+fossil timeline
200
+test wiki-30 {[string match *Unique*technote* $RESULT]}
201
+
202
+###############################################################################
203
+# Check for a collision between an attachment and a note, this was a
204
+# bug that resulted from some code treating the attachment entry as if it
205
+# were a technote when it isn't really.
206
+#
207
+# First, wait for the top of the next second so the attachment
208
+# happens at a known time, then add an attachment to an existing note
209
+# and a new note immediately after.
210
+
211
+set t0 [clock seconds]
212
+while {$t0 == [clock seconds]} {
213
+ after 100
214
+}
215
+set t1 [clock format [clock seconds] -gmt 1 -format "%Y-%m-%d %H:%M:%S"]
216
+write_fil -%m-%d %H:%M:%S"]
217
+write_file f9 "Timestamp: $t1"
218
+fossil attachment add f9 --technote {2016-01-05 01:02:03}
219
+test wiki-31 {$CODE == 0}
220
+fossil wiki create {A CODE == 0}
221
+#
222
+# Now waste time until the next second so that the remaining tests
223
+# don't have to worry about a potential collision
224
+set t0 [clock seconds]
225
+while {$t0 == [clock seconds]} {
226
+ after 100
227
+}
228
+
229
+###############################################################################
230
+# Check a technote with no timestamp cannot be created, but that
231
+# "now " is a valid stamp.
232
+set t2 [clock format [clock seconds] -gmt 1 -format # Copyright (c) 2016 D. Richard Hiiotelist [llength $technotelified timest ki create technotenow f7 --technote {2016-01-04 12:34:56+00:00}
233
+test wiki-29 {$CODE == 0}
234
+
235
+###############################################################################
236
+# Check a technote appears on the timeline
237
+write_file f8 "Contents of a 'unique' tech note"
238
+fossil wiki create {Unique technote} f8 --technote {2016-01-05 01:02:03}
239
+fossil timeline
240
+test wiki-30 {[string match *Unique*technote* $RESULT]}
241
+
242
+###############################################################################
243
+# Check for a collision between an attachment and a note, this was a
244
+# bug that resulted from some code treating the attachment entry as if it
245
+# were a technote when it isn't really.
246
+#
247
+# First, wait for the top of the next second so the attachment
248
+# happens at a known time, then add an attachment to an existing note
249
+# and a new note immediately after.
250
+
251
+set t0 [clock seconds]
252
+while {$t0 == [clock seconds]} {
253
+ after 100
254
+}
255
+set t1 [clock format [clock seconds] -gmt 1 -format "%Y-%m-%d %H:%M:%S"]
256
+write_file f9 "Timestamp: $t1"
257
+fossil attachment add f9 --technote {2016-01-05 01:02:03}
258
+test wiki-31 {$CODE == 0}
259
+fossil wiki create {Attachment collision} f9 --technote now
260
+test wiki-32 {$CODE == 0}
261
+#
262
+# Now waste time until the next second so that the remaining tests
263
+# don't have to worry about a potential collision
264
+set t0 [clock seconds]
265
+while {$t0 == [clock seconds]} {
266
+ after 100
267
+}
268
+
269
+###############################################################################
270
+# Check a technote with no timestamp cannot be created, but that
271
+# "now" is a valid stamp.
272
+s et t2 [clock format [clock seconds] -gmt 1 -format "%Y-%m-%d %H:%M:%S"]
273
+write_file f10 "Even unstampted notes are delivered.\nStamped $t2"
274
+fossil wiki create "Unstamped Note" f10 --technote -expectError
275
+test wiki-33 {$CODE != 0}
276
+fossil wiki create "Unstamped Note" f10 --technote now
277
+test wiki-34 {$CODE == 0}
278
+fossil wiki list -t
279
+test wiki-35 {[string match "*$t2*" $RESULT]}
280
+
281
+###############################################################################
282
+# Check an attachment to it in the same second works.
283
+write_file f11 "Time Stamp was $t2"
284
+fossil attachment add f11 --technote $t2
285
+test wiki-36 {$CODE == 0}
286
+fossil timeline
287
+test wiki-36-1 {$CODE == 0}
288
+fossil wiki list -t
289
+test wiki-36-2 {$CODE == 0}
290
+
291
+########################################################################### #technotelist [split $RESULT "\n"]
292
+for {set i 0} {$i < [llength $technotelist]} {incr i} {
293
+ set fullid [lindex $technotelist $i]
294
+ set id [string range $fullid 0 3]
295
+ dict incr idcounts $id
296
+ if {[dict get $idcounts $id] > $maxcount} {
297
+ set maxid $id
298
+ incr maxcount
299
+ }
300
+}
301
+# get i so that, as a julian date, it is in the 1800s, i.e., older than
302
+# any other tech note, but after 1 AD
303
+set i 2400000
304
+while {$maxcount < 2} {
305
+ # keep getting older
306
+ incr i -1
307
+ write_file f13 "A tech note with timestamp of jday=$i"
308
+ fossil wiki create "timestamp of $i" f13 --technote "$i"
309
+ fossil wiki list --technote --show-technote-ids
310
+ set technotelist [split $RESULT "\n"]
311
+ set oldesttechnoteid [lindex [split [lindex $technotelist [llength $technotelist]-1]] 0]
312
+ set id [string range $oldesttechnoteid 0 3]
313
+ dict incr idcounts $id
314
+ if {[dict get $idcounts $id] > $maxcount} {
315
+ set maxid $id
316
+ incr maxcount
317
+ }
318
+}
319
+# Save the duplicate id for this and later tests
320
+set duplicateid $maxid
321
+fossil wiki export a13 --technote $duplicateid -expectError
322
+test wiki-42 {$CODE != 0}
323
+
324
+###############################################################################
325
+# Check we can update technote by its id
326
+write_file f14 "Updated text for the really old tech note"
327
+fossil wiki commit {Old tech note} f14 --technote $anoldtechnoteid
328
+fossil wiki export a14 --technote $anoldtechnoteid
329
+test wiki-43 {[similar_file f14 a14]}
330
+
331
+###############################################################################
332
+# Check we can add attachments to a technote by its id
333
+fossil attachment add fa --technote $anoldtechnoteid
334
+test
--- a/test/wiki.test
+++ b/test/wiki.test
@@ -0,0 +1,334 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
--- a/test/wiki.test
+++ b/test/wiki.test
@@ -0,0 +1,334 @@
1 #
2 # Copyright (c) 2016 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
6 # known as the "2-Clause License" or "FreeBSD License".)
7 #
8 # This program is distributed in the hope that it will be useful,
9 # but without any warranty; without even the implied warranty of
10 # merchantability or fitness for a particular purpose.
11 #
12 # Author contact information:
13 # [email protected]
14 # http://www.hwaci.com/drh/
15 #
16 ############################################################################
17 #
18 # Test wiki and attachment comman (c) 2016 D. Richard Hipp
19 #
20 # This program is free software; you can redistribute it and/or
21 # modify it under the terms of the Simplified BSD License (also
22 # known as the "2buted in the hoperegsub -all { +\n} $x \n x
23 (c) 2016 D. Richard Hipp
24 #
25 # #
26 # Copyright (c) 201 that it will be usefuy \n y
27 (c) 2016 D. Richard #
28 # Coi.com
29 # http://www.hwaci.com/drh/
30 #
31 ############################################################################
32 #
33 # Test wiki and attachment command Support
34 #
35
36 test_setup
37
38 # Disable backoffice for this test, otherwise its process lingers for some
39 # time after the test has completed.
40 # Perhaps, this should be done in test_setup and enabled explicitly only
41 # when needed.
42 fossil set backoffice-disable 1
43
44 # Return true if two files are similar (i.e not only compress trailing spaces
45 # from a lset CODE [regex regsub -all { +\n} $x \n x
46 No info link found [read_file $b]
47 regsub -all { +http << "GET /artifact/$info D. Richard Hip#
48 # Copyright {a b} {
49 set x ""
50 if {[file exists $a]} {
51 set x [read_file $a]
52 regsub -all { +\n} $x \n x
53 regsub -all {\n$} $x {} x
54 }
55 set y ""
56 if {[file exists $b]} {
57 set y [read_file $b]
58 regsub -all { +\n} $y \n y
59 regsub -all {\n$} $y {} y
60 }
61 return [expr {$x==$y}]
62 }
63
64 # Return the mime type in the manifest for a given wiki page
65 # Defaults to "error: some text" if the manifest can't be located and
66 # "text/x-fossil-wiki" (the default mimetype for rendering)
67 # if the N card is omitted in the manifest.
68 # Note: Makes fossil calls, so $CODE and $RESULT will be corrupted
69 proc get_mime_type {name} {
70 global CODE RESULT
71 fossil http << "GET /wiki?name=$name"
72 if {$CODE != 0} {
73 return "error: /wiki?name=$name $CODE $RESULT"
74 }
75 fossil whatis --type w $name
76 if {$CODE != 0} {
77 return "error: fossil whatis --type w $name $CODE $RESULT"
78 }
79 set CODE [regexp -line {^artifact:\s*([0-9a-f]+)$} $RESULT match info]
80 if {$CODE == 0} {
81 return "error: whatis returned no info for wiki page $name"
82 }
83 fossil artifact $info
84 if {$CODE != 0} {
85 return "error: fossil artifact $info $CODE $RESULT"
86 }
87 set CODE [regexp -line {^N (.*)$} $RESULT match mimetype]
88 if {$CODE == 0} {
89 return "text/x-fossil-wiki"
90 }
91 return $mimetype
92 }
93
94
95 ###############################################################################
96 # Initially there should be no wiki entries
97 fossil wiki list
98 test wiki-0 {[normalize_result] eq {}}
99
100 ###############################################################################
101 # Adding an entry should add it to the wiki list
102 write_file f1 "first wiki note"
103 fossil wiki create tcltest f1
104 test wiki-1 {$CODE == 0}
105 fossil wiki list
106 test wiki-2 {[normalize_result] eq {tcltest}}
107
108 ###############################################################################
109 # Trying to add the same entry should fail
110 fossil wiki create tcltest f1 -expectError
111 test wiki-3 {$CODE != 0}
112
113 ###############################################################################
114 # exporting the wiki page should give back similar text
115 fossil wiki export tcltest a1
116 test wiki-4 {[similar_file f1 a1]}
117
118 ###############################################################################
119 # commiting a change to an existing page should replace the page on export
120 write_file f2 "second version of the page"
121 fossil wiki commit tcltest f2
122 test wiki-5 {$CODE == 0}
123 fossil wiki export tcltest a2
124 test wiki-6 {[similar_file f2 a2]}
125
126 ###############################################################################
127 # But we shouldn't be able to update non-existant pages
128 fossil wiki commit doesntexist f1 -expectError
129 test wiki-7 {$CODE != 0}
130
131 ###############################################################################
132 # There shouldn't be any tech notes at this point
133 fossil wiki list --technote
134 test wiki-8 {[normalize_result] eq {}}
135
136 ###############################################################################
137 # Creating a tech note with a specified timestamp should add a technote
138 write_file f3 "A technote"
139 f ossil wiki create technote f3 --technote {2016-01-01 12:34}
140 test wiki-9 {$CODE == 0}
141 fossil wiki list --technote
142 test wiki-10 {[normalize_result] eq {2016-01-01 12:34:00}}
143 fossil wiki list --technote --show-technote-ids
144 set technotelist [split $RESULT "\n"]
145 set veryfirsttechnoteid [lindex [split [lindex $technotelist 0]] 0]
146
147 ###############################################################################
148 # exporting that technote should give back similar text
149 fossil wiki export a3 --technote {2016-01-01 12:34:00}
150 test wiki-11 {[similar_file f3 a3]}
151
152 ###############################################################################
153 # Trying to add a technote with the same timestamp should succeed and create a
154 # second tech note
155 fossil wiki create 2ndnote f3 -technote {2016-01-01 12:34}
156 test wiki-13 {$CODE == 0}
157 fossil wiki list --technote
158 set technotelist [split $RESULT "\n"]
159 test wiki-13.1 {[llength $technotelist] == 2}
160
161 ###############################################################################
162 # commiting a change to an existing technote should replace the page on export
163 # (this should update th rt
164 # (this should update the tech note from wiki-13 as that the most recently
165 # updated one, that should also be the one exported by the export command)
166 write_file f4 "technote 2nd variant"
167 fossil wiki commit technote f4 --technote {2016-01-01 12:34}
168 test wiki-14 {$CODE == 0}
169 fossil wiki export a4 --technote {2016-01-01 12:34}
170 test wiki-15 {[similar_file f4 a4]}
171 # Also check that the tech note with the same timestamp, but modified less
172 # recently still has its original text
173 fossil wiki export a4.1 --technote $veryfirsttechnoteid
174 test wiki-15.1 {[similar_file f3 a4.1]}
175
176 ###############################################################################
177 # But we shouldn't be able to update non-existant pages
178 fossil wiki commit doesntexist f1 -expectError
179 test wiki-16 {$CODE != 0}
180
181 ###############################################################################
182 # Check specifying tags for a technote is OK
183 write_file f5 "technote with tags"
184 fossil wiki create {tagged technote} f5 --technote {2016-01-02 12:34} --technote-tags {A B}
185 test wiki-17 {$CODE == 0}
186 write_file f5.1 "editted and tagged technote"
187 fossil wiki commit {tagged technote} f5 --technote {2016-01-02 12:34} --t note {2016-01 -03 12:34} --technote-bgcolor blue
188 test wiki-28 {$CODE == 0}
189
190 ###############################################################################
191 # _file f7 "Different timestamps"
192 fossil wiki create technotenow f7 --technote {2016-01-04 12:34:56+00:00}
193 test wiki-29 {$CODE == 0}
194
195 ###############################################################################
196 # Check a technote appears on the timeline
197 write_file f8 "Contents of a 'unique' tech note"
198 fossil wiki create {Unique technote} f8 --technote {2016-01-05 01:02:03}
199 fossil timeline
200 test wiki-30 {[string match *Unique*technote* $RESULT]}
201
202 ###############################################################################
203 # Check for a collision between an attachment and a note, this was a
204 # bug that resulted from some code treating the attachment entry as if it
205 # were a technote when it isn't really.
206 #
207 # First, wait for the top of the next second so the attachment
208 # happens at a known time, then add an attachment to an existing note
209 # and a new note immediately after.
210
211 set t0 [clock seconds]
212 while {$t0 == [clock seconds]} {
213 after 100
214 }
215 set t1 [clock format [clock seconds] -gmt 1 -format "%Y-%m-%d %H:%M:%S"]
216 write_fil -%m-%d %H:%M:%S"]
217 write_file f9 "Timestamp: $t1"
218 fossil attachment add f9 --technote {2016-01-05 01:02:03}
219 test wiki-31 {$CODE == 0}
220 fossil wiki create {A CODE == 0}
221 #
222 # Now waste time until the next second so that the remaining tests
223 # don't have to worry about a potential collision
224 set t0 [clock seconds]
225 while {$t0 == [clock seconds]} {
226 after 100
227 }
228
229 ###############################################################################
230 # Check a technote with no timestamp cannot be created, but that
231 # "now " is a valid stamp.
232 set t2 [clock format [clock seconds] -gmt 1 -format # Copyright (c) 2016 D. Richard Hiiotelist [llength $technotelified timest ki create technotenow f7 --technote {2016-01-04 12:34:56+00:00}
233 test wiki-29 {$CODE == 0}
234
235 ###############################################################################
236 # Check a technote appears on the timeline
237 write_file f8 "Contents of a 'unique' tech note"
238 fossil wiki create {Unique technote} f8 --technote {2016-01-05 01:02:03}
239 fossil timeline
240 test wiki-30 {[string match *Unique*technote* $RESULT]}
241
242 ###############################################################################
243 # Check for a collision between an attachment and a note, this was a
244 # bug that resulted from some code treating the attachment entry as if it
245 # were a technote when it isn't really.
246 #
247 # First, wait for the top of the next second so the attachment
248 # happens at a known time, then add an attachment to an existing note
249 # and a new note immediately after.
250
251 set t0 [clock seconds]
252 while {$t0 == [clock seconds]} {
253 after 100
254 }
255 set t1 [clock format [clock seconds] -gmt 1 -format "%Y-%m-%d %H:%M:%S"]
256 write_file f9 "Timestamp: $t1"
257 fossil attachment add f9 --technote {2016-01-05 01:02:03}
258 test wiki-31 {$CODE == 0}
259 fossil wiki create {Attachment collision} f9 --technote now
260 test wiki-32 {$CODE == 0}
261 #
262 # Now waste time until the next second so that the remaining tests
263 # don't have to worry about a potential collision
264 set t0 [clock seconds]
265 while {$t0 == [clock seconds]} {
266 after 100
267 }
268
269 ###############################################################################
270 # Check a technote with no timestamp cannot be created, but that
271 # "now" is a valid stamp.
272 s et t2 [clock format [clock seconds] -gmt 1 -format "%Y-%m-%d %H:%M:%S"]
273 write_file f10 "Even unstampted notes are delivered.\nStamped $t2"
274 fossil wiki create "Unstamped Note" f10 --technote -expectError
275 test wiki-33 {$CODE != 0}
276 fossil wiki create "Unstamped Note" f10 --technote now
277 test wiki-34 {$CODE == 0}
278 fossil wiki list -t
279 test wiki-35 {[string match "*$t2*" $RESULT]}
280
281 ###############################################################################
282 # Check an attachment to it in the same second works.
283 write_file f11 "Time Stamp was $t2"
284 fossil attachment add f11 --technote $t2
285 test wiki-36 {$CODE == 0}
286 fossil timeline
287 test wiki-36-1 {$CODE == 0}
288 fossil wiki list -t
289 test wiki-36-2 {$CODE == 0}
290
291 ########################################################################### #technotelist [split $RESULT "\n"]
292 for {set i 0} {$i < [llength $technotelist]} {incr i} {
293 set fullid [lindex $technotelist $i]
294 set id [string range $fullid 0 3]
295 dict incr idcounts $id
296 if {[dict get $idcounts $id] > $maxcount} {
297 set maxid $id
298 incr maxcount
299 }
300 }
301 # get i so that, as a julian date, it is in the 1800s, i.e., older than
302 # any other tech note, but after 1 AD
303 set i 2400000
304 while {$maxcount < 2} {
305 # keep getting older
306 incr i -1
307 write_file f13 "A tech note with timestamp of jday=$i"
308 fossil wiki create "timestamp of $i" f13 --technote "$i"
309 fossil wiki list --technote --show-technote-ids
310 set technotelist [split $RESULT "\n"]
311 set oldesttechnoteid [lindex [split [lindex $technotelist [llength $technotelist]-1]] 0]
312 set id [string range $oldesttechnoteid 0 3]
313 dict incr idcounts $id
314 if {[dict get $idcounts $id] > $maxcount} {
315 set maxid $id
316 incr maxcount
317 }
318 }
319 # Save the duplicate id for this and later tests
320 set duplicateid $maxid
321 fossil wiki export a13 --technote $duplicateid -expectError
322 test wiki-42 {$CODE != 0}
323
324 ###############################################################################
325 # Check we can update technote by its id
326 write_file f14 "Updated text for the really old tech note"
327 fossil wiki commit {Old tech note} f14 --technote $anoldtechnoteid
328 fossil wiki export a14 --technote $anoldtechnoteid
329 test wiki-43 {[similar_file f14 a14]}
330
331 ###############################################################################
332 # Check we can add attachments to a technote by its id
333 fossil attachment add fa --technote $anoldtechnoteid
334 test

Keyboard Shortcuts

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