Index: src/report.c ================================================================== --- src/report.c +++ src/report.c @@ -145,11 +145,11 @@ ** This is the SQLite authorizer callback used to make sure that the ** SQL statements entered by users do not try to do anything untoward. ** If anything suspicious is tried, set *(char**)pError to an error ** message obtained from malloc. */ -static int report_query_authorizer( +int report_query_authorizer( void *pError, int code, const char *zArg1, const char *zArg2, const char *zArg3, @@ -942,5 +942,169 @@ sqlite3_exec(g.db, zSql, output_tab_separated, &count, &zErr2); sqlite3_set_authorizer(g.db, 0, 0); cgi_set_content_type("text/plain"); } } + +/* +** report number for full table ticket export +*/ +static const char zFullTicketRptRn[] = "0"; + +/* +** report title for full table ticket export +*/ +static const char zFullTicketRptTitle[] = "full ticket export"; + +/* +** show all reports, which can be used for ticket show. +** Output is written to stdout as tab delimited table +*/ +void rpt_list_reports(void){ + Stmt q; + char const aRptOutFrmt[] = "%s\t%s\n"; + + printf("Available reports:\n"); + printf(aRptOutFrmt,"report number","report title"); + printf(aRptOutFrmt,zFullTicketRptRn,zFullTicketRptTitle); + db_prepare(&q,"SELECT rn,title FROM reportfmt ORDER BY rn"); + while( db_step(&q)==SQLITE_ROW ){ + const char *zRn = db_column_text(&q, 0); + const char *zTitle = db_column_text(&q, 1); + + printf(aRptOutFrmt,zRn,zTitle); + } + db_finalize(&q); +} + +/* +** user defined separator used by ticket show command +*/ +static const char *zSep = 0; + +/* +** select the quoting algorithm for "ticket show" +*/ +#if INTERFACE +typedef enum eTktShowEnc { tktNoTab=0, tktFossilize=1 } tTktShowEncoding; +#endif +static tTktShowEncoding tktEncode = tktNoTab; + +/* +** Output the text given in the argument. Convert tabs and newlines into +** spaces. +*/ +static void output_no_tabs_file(const char *z){ + switch( tktEncode ){ + case tktFossilize: + { char *zFosZ; + + if( z && *z ){ + zFosZ = fossilize(z,-1); + printf("%s",zFosZ); + free(zFosZ); + } + break; + } + default: + while( z && z[0] ){ + int i, j; + for(i=0; z[i] && (!isspace(z[i]) || z[i]==' '); i++){} + if( i>0 ){ + printf("%.*s", i, z); + } + for(j=i; isspace(z[j]); j++){} + if( j>i ){ + printf("%*s", j-i, ""); + } + z += j; + } + break; + } +} + +/* +** Output a row as a tab-separated line of text. +*/ +int output_separated_file( + void *pUser, /* Pointer to row-count integer */ + int nArg, /* Number of columns in this result row */ + char **azArg, /* Text of data in all columns */ + char **azName /* Names of the columns */ +){ + int *pCount = (int*)pUser; + int i; + + if( *pCount==0 ){ + for(i=0; i<nArg; i++){ + output_no_tabs_file(azName[i]); + printf("%s", i<nArg-1 ? (zSep?zSep:"\t") : "\n"); + } + } + ++*pCount; + for(i=0; i<nArg; i++){ + output_no_tabs_file(azArg[i]); + printf("%s", i<nArg-1 ? (zSep?zSep:"\t") : "\n"); + } + return 0; +} + +/* +** Generate a report. The rn query parameter is the report number. +** The output is written to stdout as flat file. The zFilter paramater +** is a full WHERE-condition. +*/ +void rptshow( + const char *zRep, + const char *zSepIn, + const char *zFilter, + tTktShowEncoding enc +){ + Stmt q; + char *zSql; + const char *zTitle; + const char *zOwner; + const char *zClrKey; + char *zErr1 = 0; + char *zErr2 = 0; + int count = 0; + int rn; + + if (!zRep || !strcmp(zRep,zFullTicketRptRn) || !strcmp(zRep,zFullTicketRptTitle) ){ + zTitle = zFullTicketRptTitle; + zSql = "SELECT * FROM ticket"; + zOwner = g.zLogin; + zClrKey = ""; + }else{ + rn = atoi(zRep); + if( rn ){ + db_prepare(&q, + "SELECT title, sqlcode, owner, cols FROM reportfmt WHERE rn=%d", rn); + }else{ + db_prepare(&q, + "SELECT title, sqlcode, owner, cols FROM reportfmt WHERE title='%s'", zRep); + } + if( db_step(&q)!=SQLITE_ROW ){ + db_finalize(&q); + rpt_list_reports(); + fossil_fatal("unkown report format(%s)!",zRep); + } + zTitle = db_column_malloc(&q, 0); + zSql = db_column_malloc(&q, 1); + zOwner = db_column_malloc(&q, 2); + zClrKey = db_column_malloc(&q, 3); + db_finalize(&q); + } + if( zFilter ){ + zSql = mprintf("SELECT * FROM (%s) WHERE %s",zSql,zFilter); + } + count = 0; + tktEncode = enc; + zSep = zSepIn; + sqlite3_set_authorizer(g.db, report_query_authorizer, (void*)&zErr1); + sqlite3_exec(g.db, zSql, output_separated_file, &count, &zErr2); + sqlite3_set_authorizer(g.db, 0, 0); + if( zFilter ){ + free(zSql); + } +} + Index: src/tag.c ================================================================== --- src/tag.c +++ src/tag.c @@ -355,11 +355,11 @@ ** fossil update tag:decaf ** ** will assume that "decaf" is a tag/branch name. ** ** only allow --date-override and --user-override in -** %fossil tag add --date-override 'YYYY-MMM-DD HH:MM:SS' \ +** %fossil tag add --date-override 'YYYY-MMM-DD HH:MM:SS' \\ ** --user-override user ** in order to import history from other scm systems */ void tag_cmd(void){ int n; Index: src/tkt.c ================================================================== --- src/tkt.c +++ src/tkt.c @@ -830,5 +830,238 @@ } blob_reset(&val); } @ </ol> } + +/* +** 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? +** +** Run the ticket report, identified by the report format title +** used in the gui. The data is written as flat file on stdout, +** using "," as separator. The seperator "," 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. +** +** 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 ?FIELD VALUE .. ? ?-q|--quote? +** %fossil ticket change TICKETUUID FIELD VALUE ?FIELD VALUE .. ? ?-q|--quote? +** +** change ticket identified by TICKETUUID and set the value of +** field FIELD to VALUE. Valid field descriptions are: +** status, type, severity, priority, resolution, +** foundin, private_contact, resolution, title or comment +** Field names given above are the ones, defined in a standard +** fossil environment. If you have added, deleted columns, you +** change the all your configured columns. +** 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. +** +** The values in set|add are not validated against the definitions +** given in "Ticket Common Script". +*/ +void ticket_cmd(void){ + int n; + + /* do some ints, we want to be inside a checkout */ + db_must_be_within_tree(); + db_find_and_open_repository(1); + user_select(); + /* + ** Check that the user exists. + */ + if( !db_exists("SELECT 1 FROM user WHERE login=%Q", g.zLogin) ){ + fossil_fatal("no such user: %s", g.zLogin); + } + + if( g.argc<3 ){ + usage("add|fieldlist|set|show"); + }else{ + n = strlen(g.argv[2]); + if( n==1 && g.argv[2][0]=='s' ){ + /* set/show cannot be distinguished, so show the usage */ + usage("add|fieldlist|set|show"); + }else 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",azField[i]); + } + }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,err } eCmd = err; + int i; + int rid; + const char *zTktUuid; + 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 ){ + eCmd = set; + if( g.argc==3 ){ + usage("set 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; + 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"); + } + + /* 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 azValue array */ + while( i<g.argc ){ + char *zFName; + char *zFValue; + int j; + + zFName = g.argv[i++]; + if( i==g.argc ){ + fossil_fatal("missing value for '%s'!",zFName); + } + zFValue = g.argv[i++]; + j = fieldId(zFName); + if( tktEncoding == tktFossilize ){ + zFValue=mprintf("%s",zFValue); + defossilize(zFValue); + } + if( j == -1 ){ + fossil_fatal("unknown field name '%s'!",zFName); + }else{ + azValue[j] = zFValue; + } + } + + /* now add the needed artifacts to the repository */ + blob_zero(&tktchng); + { /* add the time to the ticket manifest */ + char *zDate; + + zDate = db_text(0, "SELECT datetime('now')"); + zDate[10] = 'T'; + blob_appendf(&tktchng, "D %s\n", zDate); + free(zDate); + } + /* append defined elements */ + for(i=0; i<nField; i++){ + char *zValue; + + zValue = azValue[i]; + if( azValue[i] && azValue[i][0] ){ + if( strncmp(azField[i], "private_", 8)==0 ){ + zValue = db_conceal(zValue, strlen(zValue)); + blob_appendf(&tktchng, "J %s %s\n", azField[i], zValue); + }else{ + blob_appendf(&tktchng, "J %s %#F\n", azField[i], strlen(zValue), zValue); + } + if( tktEncoding == tktFossilize ){ + free(azValue[i]); + } + } + } + blob_appendf(&tktchng, "K %s\n", zTktUuid); + blob_appendf(&tktchng, "U %F\n", g.zLogin); + md5sum_blob(&tktchng, &cksum); + blob_appendf(&tktchng, "Z %b\n", &cksum); + rid = content_put(&tktchng, 0, 0); + if( rid==0 ){ + fossil_panic("trouble committing ticket: %s", g.zErrMsg); + } + manifest_crosslink_begin(); + manifest_crosslink(rid, &tktchng); + manifest_crosslink_end(); + printf("ticket %s succeeded for UID %s\n", + (eCmd==set?"set":"add"),zTktUuid); + } + } + } +} Index: win/Makefile.dmc ================================================================== --- win/Makefile.dmc +++ win/Makefile.dmc @@ -224,10 +224,16 @@ $(OBJDIR)\doc$O : doc_.c doc.h $(TCC) -o$@ -c doc_.c doc_.c : $(SRCDIR)\doc.c +translate$E $** > $@ + +$(OBJDIR)\event$O : event_.c event.h + $(TCC) -o$@ -c event_.c + +event_.c : $(SRCDIR)\event.c + +translate$E $** > $@ $(OBJDIR)\encode$O : encode_.c encode.h $(TCC) -o$@ -c encode_.c encode_.c : $(SRCDIR)\encode.c