/*
** Copyright (c) 2007 D. Richard Hipp
**
** This program is free software; you can redistribute it and/or
** modify it under the terms of the Simplified BSD License (also
** known as the "2-Clause License" or "FreeBSD License".)
** This program is distributed in the hope that it will be useful,
** but without any warranty; without even the implied warranty of
** merchantability or fitness for a particular purpose.
**
** Author contact information:
** drh@hwaci.com
** http://www.hwaci.com/drh/
**
*******************************************************************************
**
** This file contains code to implement the "info" command. The
** "info" command gives command-line access to information about
** the current tree, or a particular artifact or check-in.
*/
#include "config.h"
#include "info.h"
#include Deleted %h(zName) Added %h(zName) Execute permission %s(mperm?"set":"cleared") for %h(zName)
}else{
@ Changes to %h(zName) Modified %h(zName)
@ from [%S(zOld)]
@ to [%S(zNew)].
}else{
@ Execute permission %s(mperm?"set":"cleared") for
@ %h(zName)
}
}else if( zOld ){
@ Deleted %h(zName)
@ version [%S(zOld)]
}else{
@ Added %h(zName)
@ version [%S(zNew)]
}
if( showDiff ){
@
}
@
}
}
/*
** Append the difference between two RIDs to the output
*/
static void append_diff(const char *zFrom, const char *zTo){
int fromid;
int toid;
Blob from, to, out;
if( zFrom ){
fromid = uuid_to_rid(zFrom, 0);
content_get(fromid, &from);
}else{
blob_zero(&from);
}
if( zTo ){
toid = uuid_to_rid(zTo, 0);
content_get(toid, &to);
}else{
blob_zero(&to);
}
blob_zero(&out);
text_diff(&from, &to, &out, 5, 1);
@ %h(blob_str(&out))
blob_reset(&from);
blob_reset(&to);
blob_reset(&out);
}
/*
** Write a line of web-page output that shows changes that have occurred
** to a file between two check-ins.
*/
static void append_file_change_line(
const char *zName, /* Name of the file that has changed */
const char *zOld, /* blob.uuid before change. NULL for added files */
const char *zNew, /* blob.uuid after change. NULL for deletes */
int showDiff, /* Show edit diffs if true */
int mperm /* EXE permission for zNew */
){
if( !g.okHistory ){
if( zNew==0 ){
@
}
}else{
if( zOld && zNew ){
if( fossil_strcmp(zOld, zNew)!=0 ){
@
append_diff(zOld, zNew);
@
}else if( zOld && zNew ){
@
@ [diff]
}
@
append_diff(zOld, zNew);
@
SHA1 Hash: | %s(zUuid) if( g.okSetup ){ @ (Record ID: %d(rid)) } @ |
---|---|
Date: | hyperlink_to_date(zDate, " |
Original Date: | hyperlink_to_date(zOrigDate, " |
Edited User: | hyperlink_to_user(zEUser,zDate," |
Original User: | hyperlink_to_user(zUser,zDate," |
User: | hyperlink_to_user(zUser,zDate," |
Edited Comment: | %w(zEComment) |
Original Comment: | %w(zComment) |
Comment: | %w(zComment) |
Received From: | @%h(zUser) @ %h(zIpAddr) on %s(zDate) |
Timelines: | @ family if( zParent ){ @ | ancestors } if( !isLeaf ){ @ | descendants } if( zParent && !isLeaf ){ @ | both } db_prepare(&q, "SELECT substr(tag.tagname,5) FROM tagxref, tag " " WHERE rid=%d AND tagtype>0 " " AND tag.tagid=tagxref.tagid " " AND +tag.tagname GLOB 'sym-*'", rid); while( db_step(&q)==SQLITE_ROW ){ const char *zTagName = db_column_text(&q, 0); @ | %h(zTagName) } db_finalize(&q); @ |
Other Links: | @@ files if( g.okZip ){ char *zUrl = mprintf("%s/tarball/%s-%S.tar.gz?uuid=%s", g.zTop, zProjName, zUuid, zUuid); @ | Tarball @ | @ ZIP archive fossil_free(zUrl); } @ | manifest if( g.okWrite ){ @ | edit } @ | @
Version: | %s(zUuid) |
---|---|
Date: | hyperlink_to_date(zDate, " |
Record ID: | %d(rid) |
Original User: | hyperlink_to_user(zUser, zDate, " |
Commands: | @@ history @ | raw-text @ | @
%h(z)
style_footer(); } /* ** Find an checkin based on query parameter zParam and parse its ** manifest. Return the number of errors. */ static Manifest *vdiff_parse_manifest(const char *zParam, int *pRid){ int rid; *pRid = rid = name_to_rid_www(zParam); if( rid==0 ){ webpage_error("Missing \"%s\" query parameter.", zParam); return 0; } if( !is_a_version(rid) ){ webpage_error("Artifact %s is not a checkin.", P(zParam)); return 0; } return manifest_get(rid, CFTYPE_MANIFEST); } /* ** Output a description of a check-in */ void checkin_description(int rid){ Stmt q; db_prepare(&q, "SELECT datetime(mtime), coalesce(euser,user)," " coalesce(ecomment,comment), uuid" " FROM event, blob" " WHERE event.objid=%d AND type='ci'" " AND blob.rid=%d", rid, rid ); while( db_step(&q)==SQLITE_ROW ){ const char *zDate = db_column_text(&q, 0); const char *zUser = db_column_text(&q, 1); const char *zCom = db_column_text(&q, 2); const char *zUuid = db_column_text(&q, 3); @ Check-in hyperlink_to_uuid(zUuid); @ - %w(zCom) by hyperlink_to_user(zUser,zDate," on"); hyperlink_to_date(zDate, "."); } db_finalize(&q); } /* ** WEBPAGE: vdiff ** URL: /vdiff?from=UUID&to=UUID&detail=BOOLEAN ** ** Show all differences between two checkins. */ void vdiff_page(void){ int ridFrom, ridTo; int showDetail = 0; Manifest *pFrom, *pTo; ManifestFile *pFileFrom, *pFileTo; login_check_credentials(); if( !g.okRead ){ login_needed(); return; } login_anonymous_available(); pFrom = vdiff_parse_manifest("from", &ridFrom); if( pFrom==0 ) return; pTo = vdiff_parse_manifest("to", &ridTo); if( pTo==0 ) return; showDetail = atoi(PD("detail","0")); style_header("Check-in Differences"); @checkin_description(ridFrom); @
checkin_description(ridTo); @
manifest_file_rewind(pFrom); pFileFrom = manifest_file_next(pFrom, 0); manifest_file_rewind(pTo); pFileTo = manifest_file_next(pTo, 0); while( pFileFrom || pFileTo ){ int cmp; if( pFileFrom==0 ){ cmp = +1; }else if( pFileTo==0 ){ cmp = -1; }else{ cmp = fossil_strcmp(pFileFrom->zName, pFileTo->zName); } if( cmp<0 ){ append_file_change_line(pFileFrom->zName, pFileFrom->zUuid, 0, 0, 0); pFileFrom = manifest_file_next(pFrom, 0); }else if( cmp>0 ){ append_file_change_line(pFileTo->zName, 0, pFileTo->zUuid, 0, manifest_file_mperm(pFileTo)); pFileTo = manifest_file_next(pTo, 0); }else if( fossil_strcmp(pFileFrom->zUuid, pFileTo->zUuid)==0 ){ /* No changes */ pFileFrom = manifest_file_next(pFrom, 0); pFileTo = manifest_file_next(pTo, 0); }else{ append_file_change_line(pFileFrom->zName, pFileFrom->zUuid, pFileTo->zUuid, showDetail, manifest_file_mperm(pFileTo)); pFileFrom = manifest_file_next(pFrom, 0); pFileTo = manifest_file_next(pTo, 0); } } manifest_destroy(pFrom); manifest_destroy(pTo); style_footer(); } /* ** Write a description of an object to the www reply. ** ** If the object is a file then mention: ** ** * It's artifact ID ** * All its filenames ** * The check-in it was part of, with times and users ** ** If the object is a manifest, then mention: ** ** * It's artifact ID ** * date of check-in ** * Comment & user */ void object_description( int rid, /* The artifact ID */ int linkToView, /* Add viewer link if true */ Blob *pDownloadName /* Fill with an appropriate download name */ ){ Stmt q; int cnt = 0; int nWiki = 0; char *zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid); db_prepare(&q, "SELECT filename.name, datetime(event.mtime)," " coalesce(event.ecomment,event.comment)," " coalesce(event.euser,event.user)," " b.uuid" " FROM mlink, filename, event, blob a, blob b" " WHERE filename.fnid=mlink.fnid" " AND event.objid=mlink.mid" " AND a.rid=mlink.fid" " AND b.rid=mlink.mid" " AND mlink.fid=%d", rid ); while( db_step(&q)==SQLITE_ROW ){ const char *zName = db_column_text(&q, 0); const char *zDate = db_column_text(&q, 1); const char *zCom = db_column_text(&q, 2); const char *zUser = db_column_text(&q, 3); const char *zVers = db_column_text(&q, 4); if( cnt>0 ){ @ Also file }else{ @ File } if( g.okHistory ){ @ %h(zName) }else{ @ %h(zName) } @ part of check-in hyperlink_to_uuid(zVers); @ - %w(zCom) by hyperlink_to_user(zUser,zDate," on"); hyperlink_to_date(zDate,"."); cnt++; if( pDownloadName && blob_size(pDownloadName)==0 ){ blob_append(pDownloadName, zName, -1); } } db_finalize(&q); db_prepare(&q, "SELECT substr(tagname, 6, 10000), datetime(event.mtime)," " coalesce(event.euser, event.user)" " FROM tagxref, tag, event" " WHERE tagxref.rid=%d" " AND tag.tagid=tagxref.tagid" " AND tag.tagname LIKE 'wiki-%%'" " AND event.objid=tagxref.rid", rid ); while( db_step(&q)==SQLITE_ROW ){ const char *zPagename = db_column_text(&q, 0); const char *zDate = db_column_text(&q, 1); const char *zUser = db_column_text(&q, 2); if( cnt>0 ){ @ Also wiki page }else{ @ Wiki page } if( g.okHistory ){ @ [%h(zPagename)] }else{ @ [%h(zPagename)] } @ by hyperlink_to_user(zUser,zDate," on"); hyperlink_to_date(zDate,"."); nWiki++; cnt++; if( pDownloadName && blob_size(pDownloadName)==0 ){ blob_appendf(pDownloadName, "%s.wiki", zPagename); } } db_finalize(&q); if( nWiki==0 ){ db_prepare(&q, "SELECT datetime(mtime), user, comment, type, uuid, tagid" " FROM event, blob" " WHERE event.objid=%d" " AND blob.rid=%d", rid, rid ); while( db_step(&q)==SQLITE_ROW ){ const char *zDate = db_column_text(&q, 0); const char *zUser = db_column_text(&q, 1); const char *zCom = db_column_text(&q, 2); const char *zType = db_column_text(&q, 3); const char *zUuid = db_column_text(&q, 4); if( cnt>0 ){ @ Also } if( zType[0]=='w' ){ @ Wiki edit }else if( zType[0]=='t' ){ @ Ticket change }else if( zType[0]=='c' ){ @ Manifest of check-in }else if( zType[0]=='e' ){ @ Instance of event hyperlink_to_event_tagid(db_column_int(&q, 5)); }else{ @ Control file referencing } if( zType[0]!='e' ){ hyperlink_to_uuid(zUuid); } @ - %w(zCom) by hyperlink_to_user(zUser,zDate," on"); hyperlink_to_date(zDate, "."); if( pDownloadName && blob_size(pDownloadName)==0 ){ blob_appendf(pDownloadName, "%.10s.txt", zUuid); } cnt++; } db_finalize(&q); } db_prepare(&q, "SELECT target, filename, datetime(mtime), user, src" " FROM attachment" " WHERE src=(SELECT uuid FROM blob WHERE rid=%d)" " ORDER BY mtime DESC /*sort*/", rid ); while( db_step(&q)==SQLITE_ROW ){ const char *zTarget = db_column_text(&q, 0); const char *zFilename = db_column_text(&q, 1); const char *zDate = db_column_text(&q, 2); const char *zUser = db_column_text(&q, 3); /* const char *zSrc = db_column_text(&q, 4); */ if( cnt>0 ){ @ Also attachment "%h(zFilename)" to }else{ @ Attachment "%h(zFilename)" to } if( strlen(zTarget)==UUID_SIZE && validate16(zTarget,UUID_SIZE) ){ if( g.okHistory && g.okRdTkt ){ @ ticket [%S(zTarget)] }else{ @ ticket [%S(zTarget)] } }else{ if( g.okHistory && g.okRdWiki ){ @ wiki page [%h(zTarget)] }else{ @ wiki page [%h(zTarget)] } } @ added by hyperlink_to_user(zUser,zDate," on"); hyperlink_to_date(zDate,"."); cnt++; if( pDownloadName && blob_size(pDownloadName)==0 ){ blob_append(pDownloadName, zFilename, -1); } } db_finalize(&q); if( cnt==0 ){ @ Control artifact. if( pDownloadName && blob_size(pDownloadName)==0 ){ blob_appendf(pDownloadName, "%.10s.txt", zUuid); } }else if( linkToView && g.okHistory ){ @ [view] } } /* ** WEBPAGE: fdiff ** URL: fdiff?v1=UUID&v2=UUID&patch ** ** Two arguments, v1 and v2, identify the files to be diffed. Show the ** difference between the two artifacts. Generate plaintext if "patch" ** is present. */ void diff_page(void){ int v1, v2; int isPatch; Blob c1, c2, diff, *pOut; login_check_credentials(); if( !g.okRead ){ login_needed(); return; } v1 = name_to_rid_www("v1"); v2 = name_to_rid_www("v2"); if( v1==0 || v2==0 ) fossil_redirect_home(); isPatch = P("patch")!=0; if( isPatch ){ pOut = cgi_output_blob(); cgi_set_content_type("text/plain"); }else{ blob_zero(&diff); pOut = &diff; } content_get(v1, &c1); content_get(v2, &c2); text_diff(&c1, &c2, pOut, 4, 1); blob_reset(&c1); blob_reset(&c2); if( !isPatch ){ style_header("Diff"); style_submenu_element("Patch", "Patch", "%s/fdiff?v1=%T&v2=%T&patch", g.zTop, P("v1"), P("v2")); @
@object_description(v1, 1, 0); @
@object_description(v2, 1, 0); @
blob_reset(&diff); style_footer(); } } /* ** WEBPAGE: raw ** URL: /raw?name=ARTIFACTID&m=TYPE ** ** Return the uninterpreted content of an artifact. Used primarily ** to view artifacts that are images. */ void rawartifact_page(void){ int rid; const char *zMime; Blob content; rid = name_to_rid_www("name"); zMime = PD("m","application/x-fossil-artifact"); login_check_credentials(); if( !g.okRead ){ login_needed(); return; } if( rid==0 ) fossil_redirect_home(); content_get(rid, &content); cgi_set_content_type(zMime); cgi_set_content(&content); } /* ** Render a hex dump of a file. */ static void hexdump(Blob *pBlob){ const unsigned char *x; int n, i, j, k; char zLine[100]; static const char zHex[] = "0123456789abcdef"; x = (const unsigned char*)blob_buffer(pBlob); n = blob_size(pBlob); for(i=0; i@ %h(blob_str(&diff)) @
@blob_zero(&downloadName); object_description(rid, 0, &downloadName); style_submenu_element("Download", "Download", "%s/raw/%T?name=%s", g.zTop, blob_str(&downloadName), zUuid); @
style_footer(); } /* ** Look for "ci" and "filename" query parameters. If found, try to ** use them to extract the record ID of an artifact for the file. */ int artifact_from_ci_and_filename(void){ const char *zFilename; const char *zCI; int cirid; Manifest *pManifest; ManifestFile *pFile; zCI = P("ci"); if( zCI==0 ) return 0; zFilename = P("filename"); if( zFilename==0 ) return 0; cirid = name_to_rid_www("ci"); pManifest = manifest_get(cirid, CFTYPE_MANIFEST); if( pManifest==0 ) return 0; manifest_file_rewind(pManifest); while( (pFile = manifest_file_next(pManifest,0))!=0 ){ if( fossil_strcmp(zFilename, pFile->zName)==0 ){ int rid = db_int(0, "SELECT rid FROM blob WHERE uuid=%Q", pFile->zUuid); manifest_destroy(pManifest); return rid; } } return 0; } /* ** The "z" argument is a string that contains the text of a source code ** file. This routine appends that text to the HTTP reply with line numbering. ** ** zLn is the ?ln= parameter for the HTTP query. If there is an argument, ** then highlight that line number and scroll to it once the page loads. ** If there are two line numbers, highlight the range of lines. */ static void output_text_with_line_numbers( const char *z, const char *zLn ){ int iStart, iEnd; /* Start and end of region to highlight */ int n = 0; /* Current line number */ int i; /* Loop index */ int iTop = 0; /* Scroll so that this line is on top of screen. */ iStart = iEnd = atoi(zLn); if( iStart>0 ){ for(i=0; fossil_isdigit(zLn[i]); i++){} if( zLn[i]==',' || zLn[i]=='-' || zLn[i]=='.' ){ i++; while( zLn[i]=='.' ){ i++; } iEnd = atoi(&zLn[i]); } if( iEndhexdump(&content); @
while( z[0] ){
n++;
for(i=0; z[i] && z[i]!='\n'; i++){}
if( n==iTop ) cgi_append_content("", -1);
if( n==iStart ){
cgi_append_content("",-1);
}
cgi_printf("%6d ", n);
if( i>0 ){
char *zHtml = htmlize(z, i);
cgi_append_content(zHtml, -1);
fossil_free(zHtml);
}
if( n==iStart-15 ) cgi_append_content("", -1);
if( n==iEnd ) cgi_append_content("", -1);
else cgi_append_content("\n", 1);
z += i;
if( z[0]=='\n' ) z++;
}
if( n");
@
if( iStart ){
@
}
}
/*
** WEBPAGE: artifact
** URL: /artifact?name=ARTIFACTID
** URL: /artifact?ci=CHECKIN&filename=PATH
**
** Show the complete content of a file identified by ARTIFACTID
** as preformatted text.
*/
void artifact_page(void){
int rid = 0;
Blob content;
const char *zMime;
Blob downloadName;
int renderAsWiki = 0;
int renderAsHtml = 0;
const char *zUuid;
if( P("ci") && P("filename") ){
rid = artifact_from_ci_and_filename();
}
if( rid==0 ){
rid = name_to_rid_www("name");
}
login_check_credentials();
if( !g.okRead ){ login_needed(); return; }
if( rid==0 ) fossil_redirect_home();
if( g.okAdmin ){
const char *zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", rid);
if( db_exists("SELECT 1 FROM shun WHERE uuid='%s'", zUuid) ){
style_submenu_element("Unshun","Unshun", "%s/shun?uuid=%s&sub=1",
g.zTop, zUuid);
}else{
style_submenu_element("Shun","Shun", "%s/shun?shun=%s#addshun",
g.zTop, zUuid);
}
}
style_header("Artifact Content");
zUuid = db_text("?", "SELECT uuid FROM blob WHERE rid=%d", rid);
@ @blob_zero(&downloadName); object_description(rid, 0, &downloadName); style_submenu_element("Download", "Download", "%s/raw/%T?name=%s", g.zTop, blob_str(&downloadName), zUuid); zMime = mimetype_from_name(blob_str(&downloadName)); if( zMime ){ if( fossil_strcmp(zMime, "text/html")==0 ){ if( P("txt") ){ style_submenu_element("Html", "Html", "%s/artifact?name=%s", g.zTop, zUuid); }else{ renderAsHtml = 1; style_submenu_element("Text", "Text", "%s/artifact?name=%s&txt=1", g.zTop, zUuid); } }else if( fossil_strcmp(zMime, "application/x-fossil-wiki")==0 ){ if( P("txt") ){ style_submenu_element("Wiki", "Wiki", "%s/artifact?name=%s", g.zTop, zUuid); }else{ renderAsWiki = 1; style_submenu_element("Text", "Text", "%s/artifact?name=%s&txt=1", g.zTop, zUuid); } } } @
if( zMime==0 ){ const char *zLn = P("ln"); const char *z = blob_str(&content); if( zLn ){ output_text_with_line_numbers(z, zLn); }else{ @} style_footer(); } /* ** WEBPAGE: tinfo ** URL: /tinfo?name=ARTIFACTID ** ** Show the details of a ticket change control artifact. */ void tinfo_page(void){ int rid; char *zDate; const char *zUuid; char zTktName[20]; Manifest *pTktChng; login_check_credentials(); if( !g.okRdTkt ){ login_needed(); return; } rid = name_to_rid_www("name"); if( rid==0 ){ fossil_redirect_home(); } zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", rid); if( g.okAdmin ){ if( db_exists("SELECT 1 FROM shun WHERE uuid='%s'", zUuid) ){ style_submenu_element("Unshun","Unshun", "%s/shun?uuid=%s&sub=1", g.zTop, zUuid); }else{ style_submenu_element("Shun","Shun", "%s/shun?shun=%s#addshun", g.zTop, zUuid); } } pTktChng = manifest_get(rid, CFTYPE_TICKET); if( pTktChng==0 ){ fossil_redirect_home(); } style_header("Ticket Change Details"); zDate = db_text(0, "SELECT datetime(%.12f)", pTktChng->rDate); memcpy(zTktName, pTktChng->zTicketUuid, 10); zTktName[10] = 0; if( g.okHistory ){ @@ %h(z) @} }else if( strncmp(zMime, "image/", 6)==0 ){ @}else{ @ (file is %d(blob_size(&content)) bytes of binary data) } @
By %h(pTktChng->zUser) on %s(zDate). See also: @ artifact content, and @ ticket @ history
}else{ @By %h(pTktChng->zUser) on %s(zDate). @
} @ @No such object: %h(zName)
style_footer(); return; } if( db_exists("SELECT 1 FROM mlink WHERE mid=%d", rid) ){ ci_page(); }else if( db_exists("SELECT 1 FROM tagxref JOIN tag USING(tagid)" " WHERE rid=%d AND tagname LIKE 'wiki-%%'", rid) ){ winfo_page(); }else if( db_exists("SELECT 1 FROM tagxref JOIN tag USING(tagid)" " WHERE rid=%d AND tagname LIKE 'tkt-%%'", rid) ){ tinfo_page(); }else if( db_exists("SELECT 1 FROM plink WHERE cid=%d", rid) ){ ci_page(); }else if( db_exists("SELECT 1 FROM plink WHERE pid=%d", rid) ){ ci_page(); }else { artifact_page(); } } /* ** Generate HTML that will present the user with a selection of ** potential background colors for timeline entries. */ void render_color_chooser( int fPropagate, /* Default value for propagation */ const char *zDefaultColor, /* The current default color */ const char *zIdPropagate, /* ID of form element checkbox. NULL for none */ const char *zId, /* The ID of the form element */ const char *zIdCustom /* ID of text box for custom color */ ){ static const struct SampleColors { const char *zCName; const char *zColor; } aColor[] = { { "(none)", "" }, { "#f2dcdc", 0 }, { "#bde5d6", 0 }, { "#a0a0a0", 0 }, { "#b0b0b0", 0 }, { "#c0c0c0", 0 }, { "#d0d0d0", 0 }, { "#e0e0e0", 0 }, { "#c0ffc0", 0 }, { "#c0fff0", 0 }, { "#c0f0ff", 0 }, { "#d0c0ff", 0 }, { "#ffc0ff", 0 }, { "#ffc0d0", 0 }, { "#fff0c0", 0 }, { "#f0ffc0", 0 }, { "#a8d3c0", 0 }, { "#a8c7d3", 0 }, { "#aaa8d3", 0 }, { "#cba8d3", 0 }, { "#d3a8bc", 0 }, { "#d3b5a8", 0 }, { "#d1d3a8", 0 }, { "#b1d3a8", 0 }, { "#8eb2a1", 0 }, { "#8ea7b2", 0 }, { "#8f8eb2", 0 }, { "#ab8eb2", 0 }, { "#b28e9e", 0 }, { "#b2988e", 0 }, { "#b0b28e", 0 }, { "#95b28e", 0 }, { "custom", "##" }, }; int nColor = sizeof(aColor)/sizeof(aColor[0])-1; int stdClrFound = 0; int i; @if( fPropagate ){ @ }else{ @ } @ Propagate color to descendants | ||||||||||||
} if( fossil_strcmp(zDefaultColor, zClr)==0 ){ @ stdClrFound=1; }else{ @ } @ %h(aColor[i].zCName) | if( (i%8)==7 && i+1||||||||||||
@ }else{ @ | @ } @ %h(aColor[i].zCName) @ @ | @
@@if( zNewColor && zNewColor[0] ){ @
@}else{ @ } wiki_convert(&comment, 0, WIKI_INLINE); blob_zero(&suffix); blob_appendf(&suffix, "(user: %h", zNewUser); db_prepare(&q, "SELECT substr(tagname,5) FROM tagxref, tag" " WHERE tagname GLOB 'sym-*' AND tagxref.rid=%d" " AND tagtype>1 AND tag.tagid=tagxref.tagid", rid); while( db_step(&q)==SQLITE_ROW ){ const char *zTag = db_column_text(&q, 0); if( nTag==0 ){ blob_appendf(&suffix, ", tags: %h", zTag); }else{ blob_appendf(&suffix, ", %h", zTag); } nTag++; } db_finalize(&q); blob_appendf(&suffix, ")"); @ %s(blob_str(&suffix)) @
Make changes to attributes of check-in @ [%s(zUuid)]:
@