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