/* ** WEBPAGE: tktedit ** WEBPAGE: debug_tktedit ** ** Edit a ticket. The ticket is identified by the name CGI parameter. ** /tktedit is the official page. The /debug_tktedit page does the same ** thing except that it does not save the ticket change record when you ** press submit - it instead prints the ticket change record at the top ** of the page. The /debug_tktedit page is intended to be used when ** debugging ticket configurations. */ void tktedit_page(void){ const char *zScript; int nName; const char *zName; int nRec; login_check_credentials(); if( !g.perm.ApndTkt && !g.perm.WrTkt ){ login_needed(); return; } zName = P("name"); if( P("cancel") ){ cgi_redirectf("tktview?name=%T", zName); } style_header("Edit Ticket"); if( zName==0 || (nName = strlen(zName))<4 || nName>UUID_SIZE || !validate16(zName,nName) ){ cgi_printf("<span class=\"tktError\">Not a valid ticket id: \\\"%h\\\"</span>\n",(zName)); style_footer(); return; } nRec = db_int(0, "SELECT count(*) FROM ticket WHERE tkt_uuid GLOB '%q*'", zName); if( nRec==0 ){ cgi_printf("<span class=\"tktError\">No such ticket: \\\"%h\\\"</span>\n",(zName)); style_footer(); return; } if( nRec>1 ){ cgi_printf("<span class=\"tktError\">%d tickets begin with:\n" "\\\"%h\\\"</span>\n",(nRec),(zName)); style_footer(); return; } if( g.thTrace ) Th_Trace("BEGIN_TKTEDIT<br />\n", -1); ticket_init(); getAllTicketFields(); initializeVariablesFromCGI(); initializeVariablesFromDb(); if( g.zPath[0]=='d' ) showAllFields(); form_begin(0, "%R/%s", g.zPath); cgi_printf("<input type=\"hidden\" name=\"name\" value=\"%s\" />\n",(zName)); login_insert_csrf_secret(); zScript = ticket_editpage_code(); Th_Store("login", g.zLogin ? g.zLogin : "******"); Th_Store("date", db_text(0, "SELECT datetime('now')")); Th_CreateCommand(g.interp, "append_field", appendRemarkCmd, 0, 0); Th_CreateCommand(g.interp, "submit_ticket", submitTicketCmd, (void*)&zName,0); if( g.thTrace ) Th_Trace("BEGIN_TKTEDIT_SCRIPT<br />\n", -1); if( Th_Render(zScript)==TH_RETURN && !g.thTrace && zName ){ cgi_redirect(mprintf("%s/tktview/%s", g.zTop, zName)); return; } captcha_generate(0); cgi_printf("</form>\n"); if( g.thTrace ) Th_Trace("BEGIN_TKTEDIT<br />\n", -1); style_footer(); }
/* ** Add a control record to the repository that either creates ** or cancels a tag. */ void tag_add_artifact( const char *zPrefix, /* Prefix to prepend to tag name */ const char *zTagname, /* The tag to add or cancel */ const char *zObjName, /* Name of object attached to */ const char *zValue, /* Value for the tag. Might be NULL */ int tagtype, /* 0:cancel 1:singleton 2:propagated */ const char *zDateOvrd, /* Override date string */ const char *zUserOvrd /* Override user name */ ){ int rid; int nrid; char *zDate; Blob uuid; Blob ctrl; Blob cksum; static const char zTagtype[] = { '-', '+', '*' }; assert( tagtype>=0 && tagtype<=2 ); user_select(); blob_zero(&uuid); blob_append(&uuid, zObjName, -1); if( name_to_uuid(&uuid, 9, "*") ){ fossil_fatal("%s", g.zErrMsg); return; } rid = name_to_rid(blob_str(&uuid)); g.markPrivate = content_is_private(rid); blob_zero(&ctrl); #if 0 if( validate16(zTagname, strlen(zTagname)) ){ fossil_fatal( "invalid tag name \"%s\" - might be confused with" " a hexadecimal artifact ID", zTagname ); } #endif zDate = date_in_standard_format(zDateOvrd ? zDateOvrd : "now"); blob_appendf(&ctrl, "D %s\n", zDate); blob_appendf(&ctrl, "T %c%s%F %b", zTagtype[tagtype], zPrefix, zTagname, &uuid); if( tagtype>0 && zValue && zValue[0] ){ blob_appendf(&ctrl, " %F\n", zValue); }else{ blob_appendf(&ctrl, "\n"); } blob_appendf(&ctrl, "U %F\n", zUserOvrd ? zUserOvrd : g.zLogin); md5sum_blob(&ctrl, &cksum); blob_appendf(&ctrl, "Z %b\n", &cksum); nrid = content_put(&ctrl); manifest_crosslink(nrid, &ctrl); assert( blob_is_reset(&ctrl) ); }
/* ** Repopulate the ticket table */ void ticket_rebuild(void){ Stmt q; ticket_create_table(1); db_begin_transaction(); db_prepare(&q,"SELECT tagname FROM tag WHERE tagname GLOB 'tkt-*'"); while( db_step(&q)==SQLITE_ROW ){ const char *zName = db_column_text(&q, 0); int len; zName += 4; len = strlen(zName); if( len<20 || !validate16(zName, len) ) continue; ticket_rebuild_entry(zName); } db_finalize(&q); db_end_transaction(0); }
/* ** Given a UUID, return the corresponding record ID. If the UUID ** does not exist, then return 0. ** ** For this routine, the UUID must be exact. For a match against ** user input with mixed case, use resolve_uuid(). ** ** If the UUID is not found and phantomize is 1 or 2, then attempt to ** create a phantom record. A private phantom is created for 2 and ** a public phantom is created for 1. */ int uuid_to_rid(const char *zUuid, int phantomize){ int rid, sz; char z[UUID_SIZE+1]; sz = strlen(zUuid); if( sz!=UUID_SIZE || !validate16(zUuid, sz) ){ return 0; } memcpy(z, zUuid, UUID_SIZE+1); canonical16(z, sz); rid = fast_uuid_to_rid(z); if( rid==0 && phantomize ){ rid = content_new(zUuid, phantomize-1); } return rid; }
/* ** name_collisions searches through events, blobs, and tickets for ** collisions of a given UUID based on its length on UUIDs no shorter ** than 4 characters in length. */ int name_collisions(const char *zName){ int c = 0; /* count of collisions for zName */ int nLen; /* length of zName */ nLen = strlen(zName); if( nLen>=4 && nLen<=UUID_SIZE && validate16(zName, nLen) ){ c = db_int(0, "SELECT" " (SELECT count(*) FROM ticket" " WHERE tkt_uuid GLOB '%q*') +" " (SELECT count(*) FROM tag" " WHERE tagname GLOB 'event-%q*') +" " (SELECT count(*) FROM blob" " WHERE uuid GLOB '%q*');", zName, zName, zName ); if( c<2 ) c = 0; } return c; }
/* ** Returns TRUE if zSym is exactly UUID_SIZE bytes long and contains ** only lower-case ASCII hexadecimal values. */ int fossil_is_uuid(const char *zSym){ return zSym && (UUID_SIZE==strlen(zSym)) && validate16(zSym, UUID_SIZE); }
/* ** Convert a symbolic name into a RID. Acceptable forms: ** ** * SHA1 hash ** * SHA1 hash prefix of at least 4 characters ** * Symbolic Name ** * "tag:" + symbolic name ** * Date or date-time ** * "date:" + Date or date-time ** * symbolic-name ":" date-time ** * "tip" ** ** The following additional forms are available in local checkouts: ** ** * "current" ** * "prev" or "previous" ** * "next" ** ** Return the RID of the matching artifact. Or return 0 if the name does not ** match any known object. Or return -1 if the name is ambiguous. ** ** The zType parameter specifies the type of artifact: ci, t, w, e, g. ** If zType is NULL or "" or "*" then any type of artifact will serve. ** If zType is "br" then find the first check-in of the named branch ** rather than the last. ** zType is "ci" in most use cases since we are usually searching for ** a check-in. ** ** Note that the input zTag for types "t" and "e" is the SHA1 hash of ** the ticket-change or event-change artifact, not the randomly generated ** hexadecimal identifier assigned to tickets and events. Those identifiers ** live in a separate namespace. */ int symbolic_name_to_rid(const char *zTag, const char *zType){ int vid; int rid = 0; int nTag; int i; int startOfBranch = 0; if( zType==0 || zType[0]==0 ){ zType = "*"; }else if( zType[0]=='b' ){ zType = "ci"; startOfBranch = 1; } if( zTag==0 || zTag[0]==0 ) return 0; /* special keyword: "tip" */ if( fossil_strcmp(zTag, "tip")==0 && (zType[0]=='*' || zType[0]=='c') ){ rid = db_int(0, "SELECT objid" " FROM event" " WHERE type='ci'" " ORDER BY event.mtime DESC" ); if( rid ) return rid; } /* special keywords: "prev", "previous", "current", and "next" */ if( g.localOpen && (vid=db_lget_int("checkout",0))!=0 ){ if( fossil_strcmp(zTag, "current")==0 ){ rid = vid; }else if( fossil_strcmp(zTag, "prev")==0 || fossil_strcmp(zTag, "previous")==0 ){ rid = db_int(0, "SELECT pid FROM plink WHERE cid=%d AND isprim", vid); }else if( fossil_strcmp(zTag, "next")==0 ){ rid = db_int(0, "SELECT cid FROM plink WHERE pid=%d" " ORDER BY isprim DESC, mtime DESC", vid); } if( rid ) return rid; } /* Date and times */ if( memcmp(zTag, "date:", 5)==0 ){ rid = db_int(0, "SELECT objid FROM event" " WHERE mtime<=julianday(%Q,'utc') AND type GLOB '%q'" " ORDER BY mtime DESC LIMIT 1", &zTag[5], zType); return rid; } if( fossil_isdate(zTag) ){ rid = db_int(0, "SELECT objid FROM event" " WHERE mtime<=julianday(%Q,'utc') AND type GLOB '%q'" " ORDER BY mtime DESC LIMIT 1", zTag, zType); if( rid) return rid; } /* Deprecated date & time formats: "local:" + date-time and ** "utc:" + date-time */ if( memcmp(zTag, "local:", 6)==0 ){ rid = db_int(0, "SELECT objid FROM event" " WHERE mtime<=julianday(%Q) AND type GLOB '%q'" " ORDER BY mtime DESC LIMIT 1", &zTag[6], zType); return rid; } if( memcmp(zTag, "utc:", 4)==0 ){ rid = db_int(0, "SELECT objid FROM event" " WHERE mtime<=julianday('%qz') AND type GLOB '%q'" " ORDER BY mtime DESC LIMIT 1", &zTag[4], zType); return rid; } /* "tag:" + symbolic-name */ if( memcmp(zTag, "tag:", 4)==0 ){ rid = db_int(0, "SELECT event.objid, max(event.mtime)" " FROM tag, tagxref, event" " WHERE tag.tagname='sym-%q' " " AND tagxref.tagid=tag.tagid AND tagxref.tagtype>0 " " AND event.objid=tagxref.rid " " AND event.type GLOB '%q'", &zTag[4], zType ); if( startOfBranch ) rid = start_of_branch(rid,1); return rid; } /* root:TAG -> The origin of the branch */ if( memcmp(zTag, "root:", 5)==0 ){ rid = symbolic_name_to_rid(zTag+5, zType); return start_of_branch(rid, 0); } /* symbolic-name ":" date-time */ nTag = strlen(zTag); for(i=0; i<nTag-10 && zTag[i]!=':'; i++){} if( zTag[i]==':' && fossil_isdate(&zTag[i+1]) ){ char *zDate = mprintf("%s", &zTag[i+1]); char *zTagBase = mprintf("%.*s", i, zTag); int nDate = strlen(zDate); if( sqlite3_strnicmp(&zDate[nDate-3],"utc",3)==0 ){ zDate[nDate-3] = 'z'; zDate[nDate-2] = 0; } rid = db_int(0, "SELECT event.objid, max(event.mtime)" " FROM tag, tagxref, event" " WHERE tag.tagname='sym-%q' " " AND tagxref.tagid=tag.tagid AND tagxref.tagtype>0 " " AND event.objid=tagxref.rid " " AND event.mtime<=julianday(%Q)" " AND event.type GLOB '%q'", zTagBase, zDate, zType ); return rid; } /* SHA1 hash or prefix */ if( nTag>=4 && nTag<=UUID_SIZE && validate16(zTag, nTag) ){ Stmt q; char zUuid[UUID_SIZE+1]; memcpy(zUuid, zTag, nTag+1); canonical16(zUuid, nTag); rid = 0; if( zType[0]=='*' ){ db_prepare(&q, "SELECT rid FROM blob WHERE uuid GLOB '%q*'", zUuid); }else{ db_prepare(&q, "SELECT blob.rid" " FROM blob, event" " WHERE blob.uuid GLOB '%q*'" " AND event.objid=blob.rid" " AND event.type GLOB '%q'", zUuid, zType ); } if( db_step(&q)==SQLITE_ROW ){ rid = db_column_int(&q, 0); if( db_step(&q)==SQLITE_ROW ) rid = -1; } db_finalize(&q); if( rid ) return rid; } /* Symbolic name */ rid = db_int(0, "SELECT event.objid, max(event.mtime)" " FROM tag, tagxref, event" " WHERE tag.tagname='sym-%q' " " AND tagxref.tagid=tag.tagid AND tagxref.tagtype>0 " " AND event.objid=tagxref.rid " " AND event.type GLOB '%q'", zTag, zType ); if( rid>0 ){ if( startOfBranch ) rid = start_of_branch(rid,1); return rid; } /* Undocumented: numeric tags get translated directly into the RID */ if( memcmp(zTag, "rid:", 4)==0 ){ zTag += 4; for(i=0; fossil_isdigit(zTag[i]); i++){} if( zTag[i]==0 ){ if( strcmp(zType,"*")==0 ){ rid = atoi(zTag); }else{ rid = db_int(0, "SELECT event.objid" " FROM event" " WHERE event.objid=%s" " AND event.type GLOB '%q'", zTag /*safe-for-%s*/, zType); } } } return rid; }
int blob_is_uuid_n(Blob *pBlob, int n){ return blob_size(pBlob)==n && validate16(blob_buffer(pBlob), n); }
/* ** Return true if the blob contains a valid UUID_SIZE-digit base16 identifier. */ int blob_is_uuid(Blob *pBlob){ return blob_size(pBlob)==UUID_SIZE && validate16(blob_buffer(pBlob), UUID_SIZE); }
/* ** Impl of /json/artifact. This basically just determines the type of ** an artifact and forwards the real work to another function. */ cson_value * json_page_artifact(){ cson_object * pay = NULL; char const * zName = NULL; char const * zType = NULL; char const * zUuid = NULL; cson_value * entry = NULL; Blob uuid = empty_blob; int rc; int rid = 0; ArtifactDispatchEntry const * dispatcher = &ArtifactDispatchList[0]; zName = json_find_option_cstr2("name", NULL, NULL, g.json.dispatchDepth+1); if(!zName || !*zName) { json_set_err(FSL_JSON_E_MISSING_ARGS, "Missing 'name' argument."); return NULL; } if( validate16(zName, strlen(zName)) ){ if( db_exists("SELECT 1 FROM ticket WHERE tkt_uuid GLOB '%q*'", zName) ){ zType = "ticket"; goto handle_entry; } if( db_exists("SELECT 1 FROM tag WHERE tagname GLOB 'event-%q*'", zName) ){ zType = "tag"; goto handle_entry; } } blob_set(&uuid,zName); rc = name_to_uuid(&uuid,-1,"*"); /* FIXME: check for a filename if all else fails. */ if(1==rc){ g.json.resultCode = FSL_JSON_E_RESOURCE_NOT_FOUND; goto error; }else if(2==rc){ g.json.resultCode = FSL_JSON_E_AMBIGUOUS_UUID; goto error; } zUuid = blob_str(&uuid); rid = db_int(0, "SELECT rid FROM blob WHERE uuid=%Q", zUuid); if(0==rid){ g.json.resultCode = FSL_JSON_E_RESOURCE_NOT_FOUND; goto error; } if( db_exists("SELECT 1 FROM mlink WHERE mid=%d", rid) || db_exists("SELECT 1 FROM plink WHERE cid=%d", rid) || db_exists("SELECT 1 FROM plink WHERE pid=%d", rid)){ zType = "checkin"; goto handle_entry; }else if( db_exists("SELECT 1 FROM tagxref JOIN tag USING(tagid)" " WHERE rid=%d AND tagname LIKE 'wiki-%%'", rid) ){ zType = "wiki"; goto handle_entry; }else if( db_exists("SELECT 1 FROM tagxref JOIN tag USING(tagid)" " WHERE rid=%d AND tagname LIKE 'tkt-%%'", rid) ){ zType = "ticket"; goto handle_entry; }else if ( db_exists("SELECT 1 FROM mlink WHERE fid = %d", rid) ){ zType = "file"; goto handle_entry; }else{ g.json.resultCode = FSL_JSON_E_RESOURCE_NOT_FOUND; goto error; } error: assert( 0 != g.json.resultCode ); goto veryend; handle_entry: pay = cson_new_object(); assert( (NULL != zType) && "Internal dispatching error." ); for( ; dispatcher->name; ++dispatcher ){ if(0!=strcmp(dispatcher->name, zType)){ continue; }else{ entry = (*dispatcher->func)(pay, rid); break; } } if(!g.json.resultCode){ assert( NULL != entry ); assert( NULL != zType ); cson_object_set( pay, "type", json_new_string(zType) ); cson_object_set( pay, "uuid", json_new_string(zUuid) ); /*cson_object_set( pay, "name", json_new_string(zName ? zName : zUuid) );*/ /*cson_object_set( pay, "rid", cson_value_new_integer(rid) );*/ if(cson_value_is_object(entry) && (cson_value_get_object(entry) != pay)){ cson_object_set(pay, "artifact", entry); } } veryend: blob_reset(&uuid); if(g.json.resultCode && pay){ cson_free_object(pay); pay = NULL; } return cson_object_value(pay); }
/* ** COMMAND: ticket* ** Usage: %fossil ticket SUBCOMMAND ... ** ** Run various subcommands to control tickets ** ** %fossil ticket show (REPORTTITLE|REPORTNR) ?TICKETFILTER? ?options? ** ** options can be: ** ?-l|--limit LIMITCHAR? ** ?-q|--quote? ** ?-R|--repository FILE? ** ** Run the ticket report, identified by the report format title ** used in the gui. The data is written as flat file on stdout, ** using TAB as separator. The separator can be changed using ** the -l or --limit option. ** ** If TICKETFILTER is given on the commandline, the query is ** limited with a new WHERE-condition. ** example: Report lists a column # with the uuid ** TICKETFILTER may be [#]='uuuuuuuuu' ** example: Report only lists rows with status not open ** TICKETFILTER: status != 'open' ** If the option -q|--quote is used, the tickets are encoded by ** quoting special chars(space -> \\s, tab -> \\t, newline -> \\n, ** cr -> \\r, formfeed -> \\f, vtab -> \\v, nul -> \\0, \\ -> \\\\). ** Otherwise, the simplified encoding as on the show report raw ** page in the gui is used. This has no effect in JSON mode. ** ** Instead of the report title its possible to use the report ** number. Using the special report number 0 list all columns, ** defined in the ticket table. ** ** %fossil ticket list fields ** ** list all fields, defined for ticket in the fossil repository ** ** %fossil ticket list reports ** ** list all ticket reports, defined in the fossil repository ** ** %fossil ticket set TICKETUUID (FIELD VALUE)+ ?-q|--quote? ** %fossil ticket change TICKETUUID (FIELD VALUE)+ ?-q|--quote? ** ** change ticket identified by TICKETUUID and set the value of ** field FIELD to VALUE. ** ** Field names as defined in the TICKET table. By default, these ** names include: type, status, subsystem, priority, severity, foundin, ** resolution, title, and comment, but other field names can be added ** or substituted in customized installations. ** ** If you use +FIELD, the VALUE Is appended to the field FIELD. ** You can use more than one field/value pair on the commandline. ** Using -q|--quote enables the special character decoding as ** in "ticket show". So it's possible, to set multiline text or ** text with special characters. ** ** %fossil ticket add FIELD VALUE ?FIELD VALUE .. ? ?-q|--quote? ** ** like set, but create a new ticket with the given values. ** ** %fossil ticket history TICKETUUID ** ** Show the complete change history for the ticket ** ** The values in set|add are not validated against the definitions ** given in "Ticket Common Script". */ void ticket_cmd(void){ int n; const char *zUser; const char *zDate; const char *zTktUuid; /* do some ints, we want to be inside a checkout */ db_find_and_open_repository(0, 0); user_select(); zUser = find_option("user-override",0,1); if( zUser==0 ) zUser = g.zLogin; zDate = find_option("date-override",0,1); if( zDate==0 ) zDate = "now"; zDate = date_in_standard_format(zDate); zTktUuid = find_option("uuid-override",0,1); if( zTktUuid && (strlen(zTktUuid)!=40 || !validate16(zTktUuid,40)) ){ fossil_fatal("invalid --uuid-override: must be 40 characters of hex"); } /* ** Check that the user exists. */ if( !db_exists("SELECT 1 FROM user WHERE login=%Q", zUser) ){ fossil_fatal("no such user: %s", zUser); } if( g.argc<3 ){ usage("add|change|list|set|show|history"); } n = strlen(g.argv[2]); if( n==1 && g.argv[2][0]=='s' ){ /* set/show cannot be distinguished, so show the usage */ usage("add|change|list|set|show|history"); } if( strncmp(g.argv[2],"list",n)==0 ){ if( g.argc==3 ){ usage("list fields|reports"); }else{ n = strlen(g.argv[3]); if( !strncmp(g.argv[3],"fields",n) ){ /* simply show all field names */ int i; /* read all available ticket fields */ getAllTicketFields(); for(i=0; i<nField; i++){ printf("%s\n",aField[i].zName); } }else if( !strncmp(g.argv[3],"reports",n) ){ rpt_list_reports(); }else{ fossil_fatal("unknown ticket list option '%s'!",g.argv[3]); } } }else{ /* add a new ticket or set fields on existing tickets */ tTktShowEncoding tktEncoding; tktEncoding = find_option("quote","q",0) ? tktFossilize : tktNoTab; if( strncmp(g.argv[2],"show",n)==0 ){ if( g.argc==3 ){ usage("show REPORTNR"); }else{ const char *zRep = 0; const char *zSep = 0; const char *zFilterUuid = 0; zSep = find_option("limit","l",1); zRep = g.argv[3]; if( !strcmp(zRep,"0") ){ zRep = 0; } if( g.argc>4 ){ zFilterUuid = g.argv[4]; } rptshow( zRep, zSep, zFilterUuid, tktEncoding ); } }else{ /* add a new ticket or update an existing ticket */ enum { set,add,history,err } eCmd = err; int i = 0; Blob tktchng, cksum; /* get command type (set/add) and get uuid, if needed for set */ if( strncmp(g.argv[2],"set",n)==0 || strncmp(g.argv[2],"change",n)==0 || strncmp(g.argv[2],"history",n)==0 ){ if( strncmp(g.argv[2],"history",n)==0 ){ eCmd = history; }else{ eCmd = set; } if( g.argc==3 ){ usage("set|change|history TICKETUUID"); } zTktUuid = db_text(0, "SELECT tkt_uuid FROM ticket WHERE tkt_uuid GLOB '%s*'", g.argv[3] ); if( !zTktUuid ){ fossil_fatal("unknown ticket: '%s'!",g.argv[3]); } i=4; }else if( strncmp(g.argv[2],"add",n)==0 ){ eCmd = add; i = 3; if( zTktUuid==0 ){ zTktUuid = db_text(0, "SELECT lower(hex(randomblob(20)))"); } } /* none of set/add, so show the usage! */ if( eCmd==err ){ usage("add|fieldlist|set|show|history"); } /* we just handle history separately here, does not get out */ if( eCmd==history ){ Stmt q; int tagid; if ( i != g.argc ){ fossil_fatal("no other parameters expected to %s!",g.argv[2]); } tagid = db_int(0, "SELECT tagid FROM tag WHERE tagname GLOB 'tkt-%q*'", zTktUuid); if( tagid==0 ){ fossil_fatal("no such ticket %h", zTktUuid); } db_prepare(&q, "SELECT datetime(mtime%s), objid, uuid, NULL, NULL, NULL" " FROM event, blob" " WHERE objid IN (SELECT rid FROM tagxref WHERE tagid=%d)" " AND blob.rid=event.objid" " UNION " "SELECT datetime(mtime%s), attachid, uuid, src, " " filename, user" " FROM attachment, blob" " WHERE target=(SELECT substr(tagname,5) FROM tag WHERE tagid=%d)" " AND blob.rid=attachid" " ORDER BY 1 DESC", timeline_utc(), tagid, timeline_utc(), tagid ); while( db_step(&q)==SQLITE_ROW ){ Manifest *pTicket; char zShort[12]; const char *zDate = db_column_text(&q, 0); int rid = db_column_int(&q, 1); const char *zChngUuid = db_column_text(&q, 2); const char *zFile = db_column_text(&q, 4); memcpy(zShort, zChngUuid, 10); zShort[10] = 0; if( zFile!=0 ){ const char *zSrc = db_column_text(&q, 3); const char *zUser = db_column_text(&q, 5); if( zSrc==0 || zSrc[0]==0 ){ fossil_print("Delete attachment %s\n", zFile); }else{ fossil_print("Add attachment %s\n", zFile); } fossil_print(" by %s on %s\n", zUser, zDate); }else{ pTicket = manifest_get(rid, CFTYPE_TICKET, 0); if( pTicket ){ int i; fossil_print("Ticket Change by %s on %s:\n", pTicket->zUser, zDate); for(i=0; i<pTicket->nField; i++){ Blob val; const char *z; z = pTicket->aField[i].zName; blob_set(&val, pTicket->aField[i].zValue); if( z[0]=='+' ){ fossil_print(" Append to "); z++; }else{ fossil_print(" Change "); } fossil_print("%h: ",z); if( blob_size(&val)>50 || contains_newline(&val)) { fossil_print("\n ",blob_str(&val)); comment_print(blob_str(&val),4,79); }else{ fossil_print("%s\n",blob_str(&val)); } blob_reset(&val); } } manifest_destroy(pTicket); } } db_finalize(&q); return; } /* read all given ticket field/value pairs from command line */ if( i==g.argc ){ fossil_fatal("empty %s command aborted!",g.argv[2]); } getAllTicketFields(); /* read commandline and assign fields in the aField[].zValue array */ while( i<g.argc ){ char *zFName; char *zFValue; int j; int append = 0; zFName = g.argv[i++]; if( i==g.argc ){ fossil_fatal("missing value for '%s'!",zFName); } zFValue = g.argv[i++]; if( tktEncoding == tktFossilize ){ zFValue=mprintf("%s",zFValue); defossilize(zFValue); } append = (zFName[0] == '+'); if (append){ zFName++; } j = fieldId(zFName); if( j == -1 ){ fossil_fatal("unknown field name '%s'!",zFName); }else{ if (append) { aField[j].zAppend = zFValue; } else { aField[j].zValue = zFValue; } } } /* now add the needed artifacts to the repository */ blob_zero(&tktchng); /* add the time to the ticket manifest */ blob_appendf(&tktchng, "D %s\n", zDate); /* append defined elements */ for(i=0; i<nField; i++){ char *zValue = 0; char *zPfx; if (aField[i].zAppend && aField[i].zAppend[0] ){ zPfx = " +"; zValue = aField[i].zAppend; } else if( aField[i].zValue && aField[i].zValue[0] ){ zPfx = " "; zValue = aField[i].zValue; } else { continue; } if( memcmp(aField[i].zName, "private_", 8)==0 ){ zValue = db_conceal(zValue, strlen(zValue)); blob_appendf(&tktchng, "J%s%s %s\n", zPfx, aField[i].zName, zValue); }else{ blob_appendf(&tktchng, "J%s%s %#F\n", zPfx, aField[i].zName, strlen(zValue), zValue); } } blob_appendf(&tktchng, "K %s\n", zTktUuid); blob_appendf(&tktchng, "U %F\n", zUser); md5sum_blob(&tktchng, &cksum); blob_appendf(&tktchng, "Z %b\n", &cksum); if( ticket_put(&tktchng, zTktUuid, 0) ){ fossil_fatal("%s\n", g.zErrMsg); }else{ fossil_print("ticket %s succeeded for %s\n", (eCmd==set?"set":"add"),zTktUuid); } } } }