Fossil SCM

Add support fo the TICKETCHNG table in the repository database.

drh 2012-11-24 20:53 UTC ticket-enhancements
Commit 48645c39a4c1d4729b266fbf52c4ec7ae132d4ba
3 files changed +11 +163 -107 +13 -2
+11
--- src/schema.c
+++ src/schema.c
@@ -397,10 +397,21 @@
397397
@ private_contact TEXT,
398398
@ resolution TEXT,
399399
@ title TEXT,
400400
@ comment TEXT
401401
@ );
402
+@ CREATE TABLE ticketchng(
403
+@ -- Do not change any column that begins with tkt_
404
+@ tkt_id INTEGER REFERENCES ticket,
405
+@ tkt_mtime DATE,
406
+@ -- Add as many fields as required below this line
407
+@ login TEXT,
408
+@ username TEXT,
409
+@ mimetype TEXT,
410
+@ icomment TEXT
411
+@ );
412
+@ CREATE INDEX ticketchng_idx1 ON ticketchng(tkt_id, tkt_mtime);
402413
;
403414
404415
/*
405416
** Predefined tagid values
406417
*/
407418
--- src/schema.c
+++ src/schema.c
@@ -397,10 +397,21 @@
397 @ private_contact TEXT,
398 @ resolution TEXT,
399 @ title TEXT,
400 @ comment TEXT
401 @ );
 
 
 
 
 
 
 
 
 
 
 
402 ;
403
404 /*
405 ** Predefined tagid values
406 */
407
--- src/schema.c
+++ src/schema.c
@@ -397,10 +397,21 @@
397 @ private_contact TEXT,
398 @ resolution TEXT,
399 @ title TEXT,
400 @ comment TEXT
401 @ );
402 @ CREATE TABLE ticketchng(
403 @ -- Do not change any column that begins with tkt_
404 @ tkt_id INTEGER REFERENCES ticket,
405 @ tkt_mtime DATE,
406 @ -- Add as many fields as required below this line
407 @ login TEXT,
408 @ username TEXT,
409 @ mimetype TEXT,
410 @ icomment TEXT
411 @ );
412 @ CREATE INDEX ticketchng_idx1 ON ticketchng(tkt_id, tkt_mtime);
413 ;
414
415 /*
416 ** Predefined tagid values
417 */
418
+163 -107
--- src/tkt.c
+++ src/tkt.c
@@ -26,62 +26,89 @@
2626
** The list of database user-defined fields in the TICKET table.
2727
** The real table also contains some addition fields for internal
2828
** used. The internal-use fields begin with "tkt_".
2929
*/
3030
static int nField = 0;
31
-static char **azField = 0; /* Names of database fields */
32
-static char **azValue = 0; /* Original values */
33
-static char **azAppend = 0; /* Value to be appended */
31
+static struct tktFieldInfo {
32
+ char *zName; /* Name of the database field */
33
+ char *zValue; /* Value to store */
34
+ char *zAppend; /* Value to append */
35
+ unsigned mUsed; /* 01: TICKET 02: TICKETCHNG */
36
+} *aField;
37
+#define USEDBY_TICKET 01
38
+#define USEDBY_TICKETCHNG 02
39
+static int haveTicket = 0; /* True if the TICKET table exists */
40
+static int haveTicketChng = 0; /* True if the TICKETCHNG table exists */
3441
3542
/*
36
-** Compare two entries in azField for sorting purposes
43
+** Compare two entries in aField[] for sorting purposes
3744
*/
3845
static int nameCmpr(const void *a, const void *b){
39
- return fossil_strcmp(*(char**)a, *(char**)b);
46
+ return fossil_strcmp(((const struct tktFieldInfo*)a)->zName,
47
+ ((const struct tktFieldInfo*)b)->zName);
48
+}
49
+
50
+/*
51
+** Return the index into aField[] of the given field name.
52
+** Return -1 if zFieldName is not in aField[].
53
+*/
54
+static int fieldId(const char *zFieldName){
55
+ int i;
56
+ for(i=0; i<nField; i++){
57
+ if( fossil_strcmp(aField[i].zName, zFieldName)==0 ) return i;
58
+ }
59
+ return -1;
4060
}
4161
4262
/*
43
-** Obtain a list of all fields of the TICKET table. Put them
44
-** in sorted order in azField[].
63
+** Obtain a list of all fields of the TICKET and TICKETCHNG tables. Put them
64
+** in sorted order in aField[].
4565
**
46
-** Also allocate space for azValue[] and azAppend[] and initialize
47
-** all the values there to zero.
66
+** The haveTicket and haveTicketChng variables are set to 1 if the TICKET and
67
+** TICKETCHANGE tables exist, respectively.
4868
*/
4969
static void getAllTicketFields(void){
5070
Stmt q;
5171
int i;
52
- if( nField>0 ) return;
72
+ static int once = 0;
73
+ if( once ) return;
74
+ once = 1;
5375
db_prepare(&q, "PRAGMA table_info(ticket)");
5476
while( db_step(&q)==SQLITE_ROW ){
55
- const char *zField = db_column_text(&q, 1);
56
- if( strncmp(zField,"tkt_",4)==0 ) continue;
77
+ const char *zFieldName = db_column_text(&q, 1);
78
+ haveTicket = 1;
79
+ if( memcmp(zFieldName,"tkt_",4)==0 ) continue;
80
+ if( nField%10==0 ){
81
+ aField = fossil_realloc(aField, sizeof(aField[0])*(nField+10) );
82
+ }
83
+ aField[nField].zName = mprintf("%s", zFieldName);
84
+ aField[nField].mUsed = USEDBY_TICKET;
85
+ nField++;
86
+ }
87
+ db_finalize(&q);
88
+ db_prepare(&q, "PRAGMA table_info(ticketchng)");
89
+ while( db_step(&q)==SQLITE_ROW ){
90
+ const char *zFieldName = db_column_text(&q, 1);
91
+ haveTicketChng = 1;
92
+ if( memcmp(zFieldName,"tkt_",4)==0 ) continue;
93
+ if( (i = fieldId(zFieldName))>=0 ){
94
+ aField[i].mUsed |= USEDBY_TICKETCHNG;
95
+ continue;
96
+ }
5797
if( nField%10==0 ){
58
- azField = fossil_realloc(azField, sizeof(azField)*3*(nField+10) );
98
+ aField = fossil_realloc(aField, sizeof(aField[0])*(nField+10) );
5999
}
60
- azField[nField] = mprintf("%s", zField);
100
+ aField[nField].zName = mprintf("%s", zFieldName);
101
+ aField[nField].mUsed = USEDBY_TICKETCHNG;
61102
nField++;
62103
}
63104
db_finalize(&q);
64
- qsort(azField, nField, sizeof(azField[0]), nameCmpr);
65
- azAppend = &azField[nField];
66
- memset(azAppend, 0, sizeof(azAppend[0])*nField);
67
- azValue = &azAppend[nField];
68
- for(i=0; i<nField; i++){
69
- azValue[i] = "";
70
- }
71
-}
72
-
73
-/*
74
-** Return the index into azField[] of the given field name.
75
-** Return -1 if zField is not in azField[].
76
-*/
77
-static int fieldId(const char *zField){
78
- int i;
79
- for(i=0; i<nField; i++){
80
- if( fossil_strcmp(azField[i], zField)==0 ) return i;
81
- }
82
- return -1;
105
+ qsort(aField, nField, sizeof(aField[0]), nameCmpr);
106
+ for(i=0; i<nField; i++){
107
+ aField[i].zValue = "";
108
+ aField[i].zAppend = 0;
109
+ }
83110
}
84111
85112
/*
86113
** Query the database for all TICKET fields for the specific
87114
** ticket whose name is given by the "name" CGI parameter.
@@ -114,15 +141,12 @@
114141
if( zVal==0 ){
115142
zVal = "";
116143
}else if( strncmp(zName, "private_", 8)==0 ){
117144
zVal = zRevealed = db_reveal(zVal);
118145
}
119
- for(j=0; j<nField; j++){
120
- if( fossil_strcmp(azField[j],zName)==0 ){
121
- azValue[j] = mprintf("%s", zVal);
122
- break;
123
- }
146
+ if( (j = fieldId(zName))>=0 ){
147
+ aField[j].zValue = mprintf("%s", zVal);
124148
}
125149
if( Th_Fetch(zName, &size)==0 ){
126150
Th_Store(zName, zVal);
127151
}
128152
free(zRevealed);
@@ -157,56 +181,69 @@
157181
Th_Store(z, P(z));
158182
}
159183
}
160184
161185
/*
162
-** Update an entry of the TICKET table according to the information
163
-** in the control file given in p. Attempt to create the appropriate
164
-** TICKET table entry if createFlag is true. If createFlag is false,
165
-** that means we already know the entry exists and so we can save the
166
-** work of trying to create it.
186
+** Update an entry of the TICKET and TICKETCHNG tables according to the
187
+** information in the ticket artifact given in p. Attempt to create
188
+** the appropriate TICKET table entry if tktid is zero. If tktid is nonzero
189
+** then it will be the ROWID of an existing TICKET entry.
190
+**
191
+** Parameter rid is the recordID for the ticket artifact in the BLOB table.
167192
**
168
-** Return TRUE if a new TICKET entry was created and FALSE if an
169
-** existing entry was revised.
193
+** Return the new rowid of the TICKET table entry.
170194
*/
171
-int ticket_insert(const Manifest *p, int createFlag, int rid){
172
- Blob sql;
195
+static int ticket_insert(const Manifest *p, int rid, int tktid){
196
+ Blob sql1, sql2, sql3;
173197
Stmt q;
174
- int i;
175
- int rc = 0;
198
+ int i, j;
199
+ const char *zSep = "(";
176200
177
- getAllTicketFields();
178
- if( createFlag ){
179
- db_multi_exec("INSERT OR IGNORE INTO ticket(tkt_uuid, tkt_mtime) "
201
+ if( tktid==0 ){
202
+ db_multi_exec("INSERT INTO ticket(tkt_uuid, tkt_mtime) "
180203
"VALUES(%Q, 0)", p->zTicketUuid);
181
- rc = db_changes();
204
+ tktid = db_last_insert_rowid();
182205
}
183
- blob_zero(&sql);
184
- blob_appendf(&sql, "UPDATE OR REPLACE ticket SET tkt_mtime=:mtime");
206
+ blob_zero(&sql1);
207
+ blob_zero(&sql2);
208
+ blob_zero(&sql3);
209
+ blob_appendf(&sql1, "UPDATE OR REPLACE ticket SET tkt_mtime=:mtime");
185210
for(i=0; i<p->nField; i++){
186211
const char *zName = p->aField[i].zName;
187212
if( zName[0]=='+' ){
188213
zName++;
189
- if( fieldId(zName)<0 ) continue;
190
- blob_appendf(&sql,", %s=coalesce(%s,'') || %Q",
191
- zName, zName, p->aField[i].zValue);
214
+ if( (j = fieldId(zName))<0 ) continue;
215
+ if( aField[j].mUsed & USEDBY_TICKET ){
216
+ blob_appendf(&sql1,", %s=coalesce(%s,'') || %Q",
217
+ zName, zName, p->aField[i].zValue);
218
+ }
192219
}else{
193
- if( fieldId(zName)<0 ) continue;
194
- blob_appendf(&sql,", %s=%Q", zName, p->aField[i].zValue);
220
+ if( (j = fieldId(zName))<0 ) continue;
221
+ blob_appendf(&sql1,", %s=%Q", zName, p->aField[i].zValue);
222
+ }
223
+ if( aField[j].mUsed & USEDBY_TICKETCHNG ){
224
+ blob_appendf(&sql2, "%s%s", zSep, zName);
225
+ blob_appendf(&sql3, "%s%Q", p->aField[i].zValue);
226
+ zSep = ",";
195227
}
196228
if( rid>0 ){
197229
wiki_extract_links(p->aField[i].zValue, rid, 1, p->rDate, i==0, 0);
198230
}
199231
}
200
- blob_appendf(&sql, " WHERE tkt_uuid='%s' AND tkt_mtime<:mtime",
201
- p->zTicketUuid);
202
- db_prepare(&q, "%s", blob_str(&sql));
232
+ blob_appendf(&sql1, " WHERE tkt_id=%d", tktid);
233
+ db_prepare(&q, "%s", blob_str(&sql1));
203234
db_bind_double(&q, ":mtime", p->rDate);
204235
db_step(&q);
205236
db_finalize(&q);
206
- blob_reset(&sql);
207
- return rc;
237
+ blob_reset(&sql1);
238
+ if( blob_size(&sql2)>0 ){
239
+ db_multi_exec("INSERT INTO ticketchng %s) VALUES %s)",
240
+ blob_str(&sql2), blob_str(&sql3));
241
+ }
242
+ blob_reset(&sql2);
243
+ blob_reset(&sql3);
244
+ return tktid;
208245
}
209246
210247
/*
211248
** Rebuild an entire entry in the TICKET table
212249
*/
@@ -213,67 +250,78 @@
213250
void ticket_rebuild_entry(const char *zTktUuid){
214251
char *zTag = mprintf("tkt-%s", zTktUuid);
215252
int tagid = tag_findid(zTag, 1);
216253
Stmt q;
217254
Manifest *pTicket;
255
+ int tktid;
218256
int createFlag = 1;
219257
220
- fossil_free(zTag);
221
- db_multi_exec(
222
- "DELETE FROM ticket WHERE tkt_uuid=%Q", zTktUuid
223
- );
258
+ fossil_free(zTag);
259
+ getAllTicketFields();
260
+ if( haveTicket==0 ) return;
261
+ tktid = db_int(0, "SELECT tkt_id FROM ticket WHERE tkt_uuid=%Q", zTktUuid);
262
+ if( haveTicketChng ){
263
+ db_multi_exec("DELETE FROM ticketchng WHERE tkt_id=%d;", tktid);
264
+ }
265
+ db_multi_exec("DELETE FROM ticket WHERE tkt_id=%d", tktid);
266
+ tktid = 0;
224267
db_prepare(&q, "SELECT rid FROM tagxref WHERE tagid=%d ORDER BY mtime",tagid);
225268
while( db_step(&q)==SQLITE_ROW ){
226269
int rid = db_column_int(&q, 0);
227270
pTicket = manifest_get(rid, CFTYPE_TICKET);
228271
if( pTicket ){
229
- ticket_insert(pTicket, createFlag, rid);
272
+ tktid = ticket_insert(pTicket, rid, tktid);
230273
manifest_ticket_event(rid, pTicket, createFlag, tagid);
231274
manifest_destroy(pTicket);
232275
}
233276
createFlag = 0;
234277
}
235278
db_finalize(&q);
236279
}
237280
238281
/*
239
-** Create the subscript interpreter and load the "common" code.
282
+** Create the TH1 interpreter and load the "common" code.
240283
*/
241284
void ticket_init(void){
242285
const char *zConfig;
243286
Th_FossilInit(0, 0);
244287
zConfig = ticket_common_code();
245288
Th_Eval(g.interp, 0, zConfig, -1);
246289
}
247290
248291
/*
249
-** Create the subscript interpreter and load the "change" code.
292
+** Create the TH1 interpreter and load the "change" code.
250293
*/
251294
int ticket_change(void){
252295
const char *zConfig;
253296
Th_FossilInit(0, 0);
254297
zConfig = ticket_change_code();
255298
return Th_Eval(g.interp, 0, zConfig, -1);
256299
}
257300
258301
/*
259
-** Recreate the ticket table.
302
+** Recreate the TICKET and TICKETCHNG tables.
260303
*/
261304
void ticket_create_table(int separateConnection){
262305
const char *zSql;
263306
264
- db_multi_exec("DROP TABLE IF EXISTS ticket;");
307
+ db_multi_exec(
308
+ "DROP TABLE IF EXISTS ticket;"
309
+ "DROP TABLE IF EXISTS ticketchng;"
310
+ );
265311
zSql = ticket_table_schema();
266312
if( separateConnection ){
313
+ db_end_transaction(0);
267314
db_init_database(g.zRepositoryName, zSql, 0);
268315
}else{
269316
db_multi_exec("%s", zSql);
270317
}
271318
}
272319
273320
/*
274
-** Repopulate the ticket table
321
+** Repopulate the TICKET and TICKETCHNG tables from scratch using all
322
+** available ticket artifacts.
275323
*/
276324
void ticket_rebuild(void){
277325
Stmt q;
278326
ticket_create_table(1);
279327
db_begin_transaction();
@@ -373,20 +421,20 @@
373421
if( g.thTrace ){
374422
Th_Trace("append_field %#h {%#h}<br />\n",
375423
argl[1], argv[1], argl[2], argv[2]);
376424
}
377425
for(idx=0; idx<nField; idx++){
378
- if( strncmp(azField[idx], argv[1], argl[1])==0
379
- && azField[idx][argl[1]]==0 ){
426
+ if( memcmp(aField[idx].zName, argv[1], argl[1])==0
427
+ && aField[idx].zName[argl[1]]==0 ){
380428
break;
381429
}
382430
}
383431
if( idx>=nField ){
384432
Th_ErrorMessage(g.interp, "no such TICKET column: ", argv[1], argl[1]);
385433
return TH_ERROR;
386434
}
387
- azAppend[idx] = mprintf("%.*s", argl[2], argv[2]);
435
+ aField[idx].zAppend = mprintf("%.*s", argl[2], argv[2]);
388436
return TH_OK;
389437
}
390438
391439
/*
392440
** Write a ticket into the repository.
@@ -447,29 +495,31 @@
447495
blob_zero(&tktchng);
448496
zDate = date_in_standard_format("now");
449497
blob_appendf(&tktchng, "D %s\n", zDate);
450498
free(zDate);
451499
for(i=0; i<nField; i++){
452
- if( azAppend[i] ){
453
- blob_appendf(&tktchng, "J +%s %z\n", azField[i],
454
- fossilize(azAppend[i], -1));
500
+ if( aField[i].zAppend ){
501
+ blob_appendf(&tktchng, "J +%s %z\n", aField[i].zName,
502
+ fossilize(aField[i].zAppend, -1));
455503
++nJ;
456504
}
457505
}
458506
for(i=0; i<nField; i++){
459507
const char *zValue;
460508
int nValue;
461
- if( azAppend[i] ) continue;
462
- zValue = Th_Fetch(azField[i], &nValue);
509
+ if( aField[i].zAppend ) continue;
510
+ zValue = Th_Fetch(aField[i].zName, &nValue);
463511
if( zValue ){
464512
while( nValue>0 && fossil_isspace(zValue[nValue-1]) ){ nValue--; }
465
- if( strncmp(zValue, azValue[i], nValue) || strlen(azValue[i])!=nValue ){
466
- if( strncmp(azField[i], "private_", 8)==0 ){
513
+ if( memcmp(zValue, aField[i].zValue, nValue)!=0
514
+ || strlen(aField[i].zValue)!=nValue
515
+ ){
516
+ if( memcmp(aField[i].zName, "private_", 8)==0 ){
467517
zValue = db_conceal(zValue, nValue);
468
- blob_appendf(&tktchng, "J %s %s\n", azField[i], zValue);
518
+ blob_appendf(&tktchng, "J %s %s\n", aField[i].zName, zValue);
469519
}else{
470
- blob_appendf(&tktchng, "J %s %#F\n", azField[i], nValue, zValue);
520
+ blob_appendf(&tktchng, "J %s %#F\n", aField[i].zName, nValue, zValue);
471521
}
472522
nJ++;
473523
}
474524
}
475525
}
@@ -642,18 +692,23 @@
642692
sqlite3_close(db);
643693
return zErr;
644694
}
645695
rc = sqlite3_exec(db, "SELECT tkt_id, tkt_uuid, tkt_mtime FROM ticket",
646696
0, 0, 0);
647
- sqlite3_close(db);
648697
if( rc!=SQLITE_OK ){
649
- zErr = mprintf("schema fails to define a valid ticket table "
650
- "containing all required fields");
651
- return zErr;
698
+ zErr = mprintf("schema fails to define valid a TICKET "
699
+ "table containing all required fields");
700
+ }else{
701
+ rc = sqlite3_exec(db, "SELECT tkt_id, tkt_mtime FROM ticketchng", 0,0,0);
702
+ if( rc!=SQLITE_OK ){
703
+ zErr = mprintf("schema fails to define valid a TICKETCHNG "
704
+ "table containing all required fields");
705
+ }
652706
}
707
+ sqlite3_close(db);
653708
}
654
- return 0;
709
+ return zErr;
655710
}
656711
657712
/*
658713
** WEBPAGE: tkttimeline
659714
** URL: /tkttimeline?name=TICKETUUID&y=TYPE
@@ -998,11 +1053,11 @@
9981053
int i;
9991054
10001055
/* read all available ticket fields */
10011056
getAllTicketFields();
10021057
for(i=0; i<nField; i++){
1003
- printf("%s\n",azField[i]);
1058
+ printf("%s\n",aField[i].zName);
10041059
}
10051060
}else if( !strncmp(g.argv[3],"reports",n) ){
10061061
rpt_list_reports();
10071062
}else{
10081063
fossil_fatal("unknown ticket list option '%s'!",g.argv[3]);
@@ -1105,21 +1160,22 @@
11051160
zShort[10] = 0;
11061161
if( zFile!=0 ){
11071162
const char *zSrc = db_column_text(&q, 3);
11081163
const char *zUser = db_column_text(&q, 5);
11091164
if( zSrc==0 || zSrc[0]==0 ){
1110
- fossil_print("Delete attachment %h\n", zFile);
1165
+ fossil_print("Delete attachment %s\n", zFile);
11111166
}else{
1112
- fossil_print("Add attachment %h\n", zFile);
1167
+ fossil_print("Add attachment %s\n", zFile);
11131168
}
1114
- fossil_print(" by %h on %h\n", zUser, zDate);
1169
+ fossil_print(" by %s on %s\n", zUser, zDate);
11151170
}else{
11161171
pTicket = manifest_get(rid, CFTYPE_TICKET);
11171172
if( pTicket ){
11181173
int i;
11191174
1120
- fossil_print("Ticket Change by %h on %h:\n", pTicket->zUser, zDate);
1175
+ fossil_print("Ticket Change by %s on %s:\n",
1176
+ pTicket->zUser, zDate);
11211177
for(i=0; i<pTicket->nField; i++){
11221178
Blob val;
11231179
const char *z;
11241180
z = pTicket->aField[i].zName;
11251181
blob_set(&val, pTicket->aField[i].zValue);
@@ -1148,11 +1204,11 @@
11481204
/* read all given ticket field/value pairs from command line */
11491205
if( i==g.argc ){
11501206
fossil_fatal("empty %s command aborted!",g.argv[2]);
11511207
}
11521208
getAllTicketFields();
1153
- /* read commandline and assign fields in the azValue array */
1209
+ /* read commandline and assign fields in the aField[].zValue array */
11541210
while( i<g.argc ){
11551211
char *zFName;
11561212
char *zFValue;
11571213
int j;
11581214
int append = 0;
@@ -1173,13 +1229,13 @@
11731229
j = fieldId(zFName);
11741230
if( j == -1 ){
11751231
fossil_fatal("unknown field name '%s'!",zFName);
11761232
}else{
11771233
if (append) {
1178
- azAppend[j] = zFValue;
1234
+ aField[j].zAppend = zFValue;
11791235
} else {
1180
- azValue[j] = zFValue;
1236
+ aField[j].zValue = zFValue;
11811237
}
11821238
}
11831239
}
11841240
11851241
/* now add the needed artifacts to the repository */
@@ -1189,25 +1245,25 @@
11891245
/* append defined elements */
11901246
for(i=0; i<nField; i++){
11911247
char *zValue = 0;
11921248
char *zPfx;
11931249
1194
- if (azAppend[i] && azAppend[i][0] ){
1250
+ if (aField[i].zAppend && aField[i].zAppend[0] ){
11951251
zPfx = " +";
1196
- zValue = azAppend[i];
1197
- } else if( azValue[i] && azValue[i][0] ){
1252
+ zValue = aField[i].zAppend;
1253
+ } else if( aField[i].zValue && aField[i].zValue[0] ){
11981254
zPfx = " ";
1199
- zValue = azValue[i];
1255
+ zValue = aField[i].zValue;
12001256
} else {
12011257
continue;
12021258
}
1203
- if( strncmp(azField[i], "private_", 8)==0 ){
1259
+ if( memcmp(aField[i].zName, "private_", 8)==0 ){
12041260
zValue = db_conceal(zValue, strlen(zValue));
1205
- blob_appendf(&tktchng, "J%s%s %s\n", zPfx, azField[i], zValue);
1261
+ blob_appendf(&tktchng, "J%s%s %s\n", zPfx, aField[i].zName, zValue);
12061262
}else{
12071263
blob_appendf(&tktchng, "J%s%s %#F\n", zPfx,
1208
- azField[i], strlen(zValue), zValue);
1264
+ aField[i].zName, strlen(zValue), zValue);
12091265
}
12101266
}
12111267
blob_appendf(&tktchng, "K %s\n", zTktUuid);
12121268
blob_appendf(&tktchng, "U %F\n", zUser);
12131269
md5sum_blob(&tktchng, &cksum);
12141270
--- src/tkt.c
+++ src/tkt.c
@@ -26,62 +26,89 @@
26 ** The list of database user-defined fields in the TICKET table.
27 ** The real table also contains some addition fields for internal
28 ** used. The internal-use fields begin with "tkt_".
29 */
30 static int nField = 0;
31 static char **azField = 0; /* Names of database fields */
32 static char **azValue = 0; /* Original values */
33 static char **azAppend = 0; /* Value to be appended */
 
 
 
 
 
 
 
34
35 /*
36 ** Compare two entries in azField for sorting purposes
37 */
38 static int nameCmpr(const void *a, const void *b){
39 return fossil_strcmp(*(char**)a, *(char**)b);
 
 
 
 
 
 
 
 
 
 
 
 
 
40 }
41
42 /*
43 ** Obtain a list of all fields of the TICKET table. Put them
44 ** in sorted order in azField[].
45 **
46 ** Also allocate space for azValue[] and azAppend[] and initialize
47 ** all the values there to zero.
48 */
49 static void getAllTicketFields(void){
50 Stmt q;
51 int i;
52 if( nField>0 ) return;
 
 
53 db_prepare(&q, "PRAGMA table_info(ticket)");
54 while( db_step(&q)==SQLITE_ROW ){
55 const char *zField = db_column_text(&q, 1);
56 if( strncmp(zField,"tkt_",4)==0 ) continue;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
57 if( nField%10==0 ){
58 azField = fossil_realloc(azField, sizeof(azField)*3*(nField+10) );
59 }
60 azField[nField] = mprintf("%s", zField);
 
61 nField++;
62 }
63 db_finalize(&q);
64 qsort(azField, nField, sizeof(azField[0]), nameCmpr);
65 azAppend = &azField[nField];
66 memset(azAppend, 0, sizeof(azAppend[0])*nField);
67 azValue = &azAppend[nField];
68 for(i=0; i<nField; i++){
69 azValue[i] = "";
70 }
71 }
72
73 /*
74 ** Return the index into azField[] of the given field name.
75 ** Return -1 if zField is not in azField[].
76 */
77 static int fieldId(const char *zField){
78 int i;
79 for(i=0; i<nField; i++){
80 if( fossil_strcmp(azField[i], zField)==0 ) return i;
81 }
82 return -1;
83 }
84
85 /*
86 ** Query the database for all TICKET fields for the specific
87 ** ticket whose name is given by the "name" CGI parameter.
@@ -114,15 +141,12 @@
114 if( zVal==0 ){
115 zVal = "";
116 }else if( strncmp(zName, "private_", 8)==0 ){
117 zVal = zRevealed = db_reveal(zVal);
118 }
119 for(j=0; j<nField; j++){
120 if( fossil_strcmp(azField[j],zName)==0 ){
121 azValue[j] = mprintf("%s", zVal);
122 break;
123 }
124 }
125 if( Th_Fetch(zName, &size)==0 ){
126 Th_Store(zName, zVal);
127 }
128 free(zRevealed);
@@ -157,56 +181,69 @@
157 Th_Store(z, P(z));
158 }
159 }
160
161 /*
162 ** Update an entry of the TICKET table according to the information
163 ** in the control file given in p. Attempt to create the appropriate
164 ** TICKET table entry if createFlag is true. If createFlag is false,
165 ** that means we already know the entry exists and so we can save the
166 ** work of trying to create it.
 
167 **
168 ** Return TRUE if a new TICKET entry was created and FALSE if an
169 ** existing entry was revised.
170 */
171 int ticket_insert(const Manifest *p, int createFlag, int rid){
172 Blob sql;
173 Stmt q;
174 int i;
175 int rc = 0;
176
177 getAllTicketFields();
178 if( createFlag ){
179 db_multi_exec("INSERT OR IGNORE INTO ticket(tkt_uuid, tkt_mtime) "
180 "VALUES(%Q, 0)", p->zTicketUuid);
181 rc = db_changes();
182 }
183 blob_zero(&sql);
184 blob_appendf(&sql, "UPDATE OR REPLACE ticket SET tkt_mtime=:mtime");
 
 
185 for(i=0; i<p->nField; i++){
186 const char *zName = p->aField[i].zName;
187 if( zName[0]=='+' ){
188 zName++;
189 if( fieldId(zName)<0 ) continue;
190 blob_appendf(&sql,", %s=coalesce(%s,'') || %Q",
191 zName, zName, p->aField[i].zValue);
 
 
192 }else{
193 if( fieldId(zName)<0 ) continue;
194 blob_appendf(&sql,", %s=%Q", zName, p->aField[i].zValue);
 
 
 
 
 
195 }
196 if( rid>0 ){
197 wiki_extract_links(p->aField[i].zValue, rid, 1, p->rDate, i==0, 0);
198 }
199 }
200 blob_appendf(&sql, " WHERE tkt_uuid='%s' AND tkt_mtime<:mtime",
201 p->zTicketUuid);
202 db_prepare(&q, "%s", blob_str(&sql));
203 db_bind_double(&q, ":mtime", p->rDate);
204 db_step(&q);
205 db_finalize(&q);
206 blob_reset(&sql);
207 return rc;
 
 
 
 
 
 
208 }
209
210 /*
211 ** Rebuild an entire entry in the TICKET table
212 */
@@ -213,67 +250,78 @@
213 void ticket_rebuild_entry(const char *zTktUuid){
214 char *zTag = mprintf("tkt-%s", zTktUuid);
215 int tagid = tag_findid(zTag, 1);
216 Stmt q;
217 Manifest *pTicket;
 
218 int createFlag = 1;
219
220 fossil_free(zTag);
221 db_multi_exec(
222 "DELETE FROM ticket WHERE tkt_uuid=%Q", zTktUuid
223 );
 
 
 
 
 
224 db_prepare(&q, "SELECT rid FROM tagxref WHERE tagid=%d ORDER BY mtime",tagid);
225 while( db_step(&q)==SQLITE_ROW ){
226 int rid = db_column_int(&q, 0);
227 pTicket = manifest_get(rid, CFTYPE_TICKET);
228 if( pTicket ){
229 ticket_insert(pTicket, createFlag, rid);
230 manifest_ticket_event(rid, pTicket, createFlag, tagid);
231 manifest_destroy(pTicket);
232 }
233 createFlag = 0;
234 }
235 db_finalize(&q);
236 }
237
238 /*
239 ** Create the subscript interpreter and load the "common" code.
240 */
241 void ticket_init(void){
242 const char *zConfig;
243 Th_FossilInit(0, 0);
244 zConfig = ticket_common_code();
245 Th_Eval(g.interp, 0, zConfig, -1);
246 }
247
248 /*
249 ** Create the subscript interpreter and load the "change" code.
250 */
251 int ticket_change(void){
252 const char *zConfig;
253 Th_FossilInit(0, 0);
254 zConfig = ticket_change_code();
255 return Th_Eval(g.interp, 0, zConfig, -1);
256 }
257
258 /*
259 ** Recreate the ticket table.
260 */
261 void ticket_create_table(int separateConnection){
262 const char *zSql;
263
264 db_multi_exec("DROP TABLE IF EXISTS ticket;");
 
 
 
265 zSql = ticket_table_schema();
266 if( separateConnection ){
 
267 db_init_database(g.zRepositoryName, zSql, 0);
268 }else{
269 db_multi_exec("%s", zSql);
270 }
271 }
272
273 /*
274 ** Repopulate the ticket table
 
275 */
276 void ticket_rebuild(void){
277 Stmt q;
278 ticket_create_table(1);
279 db_begin_transaction();
@@ -373,20 +421,20 @@
373 if( g.thTrace ){
374 Th_Trace("append_field %#h {%#h}<br />\n",
375 argl[1], argv[1], argl[2], argv[2]);
376 }
377 for(idx=0; idx<nField; idx++){
378 if( strncmp(azField[idx], argv[1], argl[1])==0
379 && azField[idx][argl[1]]==0 ){
380 break;
381 }
382 }
383 if( idx>=nField ){
384 Th_ErrorMessage(g.interp, "no such TICKET column: ", argv[1], argl[1]);
385 return TH_ERROR;
386 }
387 azAppend[idx] = mprintf("%.*s", argl[2], argv[2]);
388 return TH_OK;
389 }
390
391 /*
392 ** Write a ticket into the repository.
@@ -447,29 +495,31 @@
447 blob_zero(&tktchng);
448 zDate = date_in_standard_format("now");
449 blob_appendf(&tktchng, "D %s\n", zDate);
450 free(zDate);
451 for(i=0; i<nField; i++){
452 if( azAppend[i] ){
453 blob_appendf(&tktchng, "J +%s %z\n", azField[i],
454 fossilize(azAppend[i], -1));
455 ++nJ;
456 }
457 }
458 for(i=0; i<nField; i++){
459 const char *zValue;
460 int nValue;
461 if( azAppend[i] ) continue;
462 zValue = Th_Fetch(azField[i], &nValue);
463 if( zValue ){
464 while( nValue>0 && fossil_isspace(zValue[nValue-1]) ){ nValue--; }
465 if( strncmp(zValue, azValue[i], nValue) || strlen(azValue[i])!=nValue ){
466 if( strncmp(azField[i], "private_", 8)==0 ){
 
 
467 zValue = db_conceal(zValue, nValue);
468 blob_appendf(&tktchng, "J %s %s\n", azField[i], zValue);
469 }else{
470 blob_appendf(&tktchng, "J %s %#F\n", azField[i], nValue, zValue);
471 }
472 nJ++;
473 }
474 }
475 }
@@ -642,18 +692,23 @@
642 sqlite3_close(db);
643 return zErr;
644 }
645 rc = sqlite3_exec(db, "SELECT tkt_id, tkt_uuid, tkt_mtime FROM ticket",
646 0, 0, 0);
647 sqlite3_close(db);
648 if( rc!=SQLITE_OK ){
649 zErr = mprintf("schema fails to define a valid ticket table "
650 "containing all required fields");
651 return zErr;
 
 
 
 
 
652 }
 
653 }
654 return 0;
655 }
656
657 /*
658 ** WEBPAGE: tkttimeline
659 ** URL: /tkttimeline?name=TICKETUUID&y=TYPE
@@ -998,11 +1053,11 @@
998 int i;
999
1000 /* read all available ticket fields */
1001 getAllTicketFields();
1002 for(i=0; i<nField; i++){
1003 printf("%s\n",azField[i]);
1004 }
1005 }else if( !strncmp(g.argv[3],"reports",n) ){
1006 rpt_list_reports();
1007 }else{
1008 fossil_fatal("unknown ticket list option '%s'!",g.argv[3]);
@@ -1105,21 +1160,22 @@
1105 zShort[10] = 0;
1106 if( zFile!=0 ){
1107 const char *zSrc = db_column_text(&q, 3);
1108 const char *zUser = db_column_text(&q, 5);
1109 if( zSrc==0 || zSrc[0]==0 ){
1110 fossil_print("Delete attachment %h\n", zFile);
1111 }else{
1112 fossil_print("Add attachment %h\n", zFile);
1113 }
1114 fossil_print(" by %h on %h\n", zUser, zDate);
1115 }else{
1116 pTicket = manifest_get(rid, CFTYPE_TICKET);
1117 if( pTicket ){
1118 int i;
1119
1120 fossil_print("Ticket Change by %h on %h:\n", pTicket->zUser, zDate);
 
1121 for(i=0; i<pTicket->nField; i++){
1122 Blob val;
1123 const char *z;
1124 z = pTicket->aField[i].zName;
1125 blob_set(&val, pTicket->aField[i].zValue);
@@ -1148,11 +1204,11 @@
1148 /* read all given ticket field/value pairs from command line */
1149 if( i==g.argc ){
1150 fossil_fatal("empty %s command aborted!",g.argv[2]);
1151 }
1152 getAllTicketFields();
1153 /* read commandline and assign fields in the azValue array */
1154 while( i<g.argc ){
1155 char *zFName;
1156 char *zFValue;
1157 int j;
1158 int append = 0;
@@ -1173,13 +1229,13 @@
1173 j = fieldId(zFName);
1174 if( j == -1 ){
1175 fossil_fatal("unknown field name '%s'!",zFName);
1176 }else{
1177 if (append) {
1178 azAppend[j] = zFValue;
1179 } else {
1180 azValue[j] = zFValue;
1181 }
1182 }
1183 }
1184
1185 /* now add the needed artifacts to the repository */
@@ -1189,25 +1245,25 @@
1189 /* append defined elements */
1190 for(i=0; i<nField; i++){
1191 char *zValue = 0;
1192 char *zPfx;
1193
1194 if (azAppend[i] && azAppend[i][0] ){
1195 zPfx = " +";
1196 zValue = azAppend[i];
1197 } else if( azValue[i] && azValue[i][0] ){
1198 zPfx = " ";
1199 zValue = azValue[i];
1200 } else {
1201 continue;
1202 }
1203 if( strncmp(azField[i], "private_", 8)==0 ){
1204 zValue = db_conceal(zValue, strlen(zValue));
1205 blob_appendf(&tktchng, "J%s%s %s\n", zPfx, azField[i], zValue);
1206 }else{
1207 blob_appendf(&tktchng, "J%s%s %#F\n", zPfx,
1208 azField[i], strlen(zValue), zValue);
1209 }
1210 }
1211 blob_appendf(&tktchng, "K %s\n", zTktUuid);
1212 blob_appendf(&tktchng, "U %F\n", zUser);
1213 md5sum_blob(&tktchng, &cksum);
1214
--- src/tkt.c
+++ src/tkt.c
@@ -26,62 +26,89 @@
26 ** The list of database user-defined fields in the TICKET table.
27 ** The real table also contains some addition fields for internal
28 ** used. The internal-use fields begin with "tkt_".
29 */
30 static int nField = 0;
31 static struct tktFieldInfo {
32 char *zName; /* Name of the database field */
33 char *zValue; /* Value to store */
34 char *zAppend; /* Value to append */
35 unsigned mUsed; /* 01: TICKET 02: TICKETCHNG */
36 } *aField;
37 #define USEDBY_TICKET 01
38 #define USEDBY_TICKETCHNG 02
39 static int haveTicket = 0; /* True if the TICKET table exists */
40 static int haveTicketChng = 0; /* True if the TICKETCHNG table exists */
41
42 /*
43 ** Compare two entries in aField[] for sorting purposes
44 */
45 static int nameCmpr(const void *a, const void *b){
46 return fossil_strcmp(((const struct tktFieldInfo*)a)->zName,
47 ((const struct tktFieldInfo*)b)->zName);
48 }
49
50 /*
51 ** Return the index into aField[] of the given field name.
52 ** Return -1 if zFieldName is not in aField[].
53 */
54 static int fieldId(const char *zFieldName){
55 int i;
56 for(i=0; i<nField; i++){
57 if( fossil_strcmp(aField[i].zName, zFieldName)==0 ) return i;
58 }
59 return -1;
60 }
61
62 /*
63 ** Obtain a list of all fields of the TICKET and TICKETCHNG tables. Put them
64 ** in sorted order in aField[].
65 **
66 ** The haveTicket and haveTicketChng variables are set to 1 if the TICKET and
67 ** TICKETCHANGE tables exist, respectively.
68 */
69 static void getAllTicketFields(void){
70 Stmt q;
71 int i;
72 static int once = 0;
73 if( once ) return;
74 once = 1;
75 db_prepare(&q, "PRAGMA table_info(ticket)");
76 while( db_step(&q)==SQLITE_ROW ){
77 const char *zFieldName = db_column_text(&q, 1);
78 haveTicket = 1;
79 if( memcmp(zFieldName,"tkt_",4)==0 ) continue;
80 if( nField%10==0 ){
81 aField = fossil_realloc(aField, sizeof(aField[0])*(nField+10) );
82 }
83 aField[nField].zName = mprintf("%s", zFieldName);
84 aField[nField].mUsed = USEDBY_TICKET;
85 nField++;
86 }
87 db_finalize(&q);
88 db_prepare(&q, "PRAGMA table_info(ticketchng)");
89 while( db_step(&q)==SQLITE_ROW ){
90 const char *zFieldName = db_column_text(&q, 1);
91 haveTicketChng = 1;
92 if( memcmp(zFieldName,"tkt_",4)==0 ) continue;
93 if( (i = fieldId(zFieldName))>=0 ){
94 aField[i].mUsed |= USEDBY_TICKETCHNG;
95 continue;
96 }
97 if( nField%10==0 ){
98 aField = fossil_realloc(aField, sizeof(aField[0])*(nField+10) );
99 }
100 aField[nField].zName = mprintf("%s", zFieldName);
101 aField[nField].mUsed = USEDBY_TICKETCHNG;
102 nField++;
103 }
104 db_finalize(&q);
105 qsort(aField, nField, sizeof(aField[0]), nameCmpr);
106 for(i=0; i<nField; i++){
107 aField[i].zValue = "";
108 aField[i].zAppend = 0;
109 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
110 }
111
112 /*
113 ** Query the database for all TICKET fields for the specific
114 ** ticket whose name is given by the "name" CGI parameter.
@@ -114,15 +141,12 @@
141 if( zVal==0 ){
142 zVal = "";
143 }else if( strncmp(zName, "private_", 8)==0 ){
144 zVal = zRevealed = db_reveal(zVal);
145 }
146 if( (j = fieldId(zName))>=0 ){
147 aField[j].zValue = mprintf("%s", zVal);
 
 
 
148 }
149 if( Th_Fetch(zName, &size)==0 ){
150 Th_Store(zName, zVal);
151 }
152 free(zRevealed);
@@ -157,56 +181,69 @@
181 Th_Store(z, P(z));
182 }
183 }
184
185 /*
186 ** Update an entry of the TICKET and TICKETCHNG tables according to the
187 ** information in the ticket artifact given in p. Attempt to create
188 ** the appropriate TICKET table entry if tktid is zero. If tktid is nonzero
189 ** then it will be the ROWID of an existing TICKET entry.
190 **
191 ** Parameter rid is the recordID for the ticket artifact in the BLOB table.
192 **
193 ** Return the new rowid of the TICKET table entry.
 
194 */
195 static int ticket_insert(const Manifest *p, int rid, int tktid){
196 Blob sql1, sql2, sql3;
197 Stmt q;
198 int i, j;
199 const char *zSep = "(";
200
201 if( tktid==0 ){
202 db_multi_exec("INSERT INTO ticket(tkt_uuid, tkt_mtime) "
 
203 "VALUES(%Q, 0)", p->zTicketUuid);
204 tktid = db_last_insert_rowid();
205 }
206 blob_zero(&sql1);
207 blob_zero(&sql2);
208 blob_zero(&sql3);
209 blob_appendf(&sql1, "UPDATE OR REPLACE ticket SET tkt_mtime=:mtime");
210 for(i=0; i<p->nField; i++){
211 const char *zName = p->aField[i].zName;
212 if( zName[0]=='+' ){
213 zName++;
214 if( (j = fieldId(zName))<0 ) continue;
215 if( aField[j].mUsed & USEDBY_TICKET ){
216 blob_appendf(&sql1,", %s=coalesce(%s,'') || %Q",
217 zName, zName, p->aField[i].zValue);
218 }
219 }else{
220 if( (j = fieldId(zName))<0 ) continue;
221 blob_appendf(&sql1,", %s=%Q", zName, p->aField[i].zValue);
222 }
223 if( aField[j].mUsed & USEDBY_TICKETCHNG ){
224 blob_appendf(&sql2, "%s%s", zSep, zName);
225 blob_appendf(&sql3, "%s%Q", p->aField[i].zValue);
226 zSep = ",";
227 }
228 if( rid>0 ){
229 wiki_extract_links(p->aField[i].zValue, rid, 1, p->rDate, i==0, 0);
230 }
231 }
232 blob_appendf(&sql1, " WHERE tkt_id=%d", tktid);
233 db_prepare(&q, "%s", blob_str(&sql1));
 
234 db_bind_double(&q, ":mtime", p->rDate);
235 db_step(&q);
236 db_finalize(&q);
237 blob_reset(&sql1);
238 if( blob_size(&sql2)>0 ){
239 db_multi_exec("INSERT INTO ticketchng %s) VALUES %s)",
240 blob_str(&sql2), blob_str(&sql3));
241 }
242 blob_reset(&sql2);
243 blob_reset(&sql3);
244 return tktid;
245 }
246
247 /*
248 ** Rebuild an entire entry in the TICKET table
249 */
@@ -213,67 +250,78 @@
250 void ticket_rebuild_entry(const char *zTktUuid){
251 char *zTag = mprintf("tkt-%s", zTktUuid);
252 int tagid = tag_findid(zTag, 1);
253 Stmt q;
254 Manifest *pTicket;
255 int tktid;
256 int createFlag = 1;
257
258 fossil_free(zTag);
259 getAllTicketFields();
260 if( haveTicket==0 ) return;
261 tktid = db_int(0, "SELECT tkt_id FROM ticket WHERE tkt_uuid=%Q", zTktUuid);
262 if( haveTicketChng ){
263 db_multi_exec("DELETE FROM ticketchng WHERE tkt_id=%d;", tktid);
264 }
265 db_multi_exec("DELETE FROM ticket WHERE tkt_id=%d", tktid);
266 tktid = 0;
267 db_prepare(&q, "SELECT rid FROM tagxref WHERE tagid=%d ORDER BY mtime",tagid);
268 while( db_step(&q)==SQLITE_ROW ){
269 int rid = db_column_int(&q, 0);
270 pTicket = manifest_get(rid, CFTYPE_TICKET);
271 if( pTicket ){
272 tktid = ticket_insert(pTicket, rid, tktid);
273 manifest_ticket_event(rid, pTicket, createFlag, tagid);
274 manifest_destroy(pTicket);
275 }
276 createFlag = 0;
277 }
278 db_finalize(&q);
279 }
280
281 /*
282 ** Create the TH1 interpreter and load the "common" code.
283 */
284 void ticket_init(void){
285 const char *zConfig;
286 Th_FossilInit(0, 0);
287 zConfig = ticket_common_code();
288 Th_Eval(g.interp, 0, zConfig, -1);
289 }
290
291 /*
292 ** Create the TH1 interpreter and load the "change" code.
293 */
294 int ticket_change(void){
295 const char *zConfig;
296 Th_FossilInit(0, 0);
297 zConfig = ticket_change_code();
298 return Th_Eval(g.interp, 0, zConfig, -1);
299 }
300
301 /*
302 ** Recreate the TICKET and TICKETCHNG tables.
303 */
304 void ticket_create_table(int separateConnection){
305 const char *zSql;
306
307 db_multi_exec(
308 "DROP TABLE IF EXISTS ticket;"
309 "DROP TABLE IF EXISTS ticketchng;"
310 );
311 zSql = ticket_table_schema();
312 if( separateConnection ){
313 db_end_transaction(0);
314 db_init_database(g.zRepositoryName, zSql, 0);
315 }else{
316 db_multi_exec("%s", zSql);
317 }
318 }
319
320 /*
321 ** Repopulate the TICKET and TICKETCHNG tables from scratch using all
322 ** available ticket artifacts.
323 */
324 void ticket_rebuild(void){
325 Stmt q;
326 ticket_create_table(1);
327 db_begin_transaction();
@@ -373,20 +421,20 @@
421 if( g.thTrace ){
422 Th_Trace("append_field %#h {%#h}<br />\n",
423 argl[1], argv[1], argl[2], argv[2]);
424 }
425 for(idx=0; idx<nField; idx++){
426 if( memcmp(aField[idx].zName, argv[1], argl[1])==0
427 && aField[idx].zName[argl[1]]==0 ){
428 break;
429 }
430 }
431 if( idx>=nField ){
432 Th_ErrorMessage(g.interp, "no such TICKET column: ", argv[1], argl[1]);
433 return TH_ERROR;
434 }
435 aField[idx].zAppend = mprintf("%.*s", argl[2], argv[2]);
436 return TH_OK;
437 }
438
439 /*
440 ** Write a ticket into the repository.
@@ -447,29 +495,31 @@
495 blob_zero(&tktchng);
496 zDate = date_in_standard_format("now");
497 blob_appendf(&tktchng, "D %s\n", zDate);
498 free(zDate);
499 for(i=0; i<nField; i++){
500 if( aField[i].zAppend ){
501 blob_appendf(&tktchng, "J +%s %z\n", aField[i].zName,
502 fossilize(aField[i].zAppend, -1));
503 ++nJ;
504 }
505 }
506 for(i=0; i<nField; i++){
507 const char *zValue;
508 int nValue;
509 if( aField[i].zAppend ) continue;
510 zValue = Th_Fetch(aField[i].zName, &nValue);
511 if( zValue ){
512 while( nValue>0 && fossil_isspace(zValue[nValue-1]) ){ nValue--; }
513 if( memcmp(zValue, aField[i].zValue, nValue)!=0
514 || strlen(aField[i].zValue)!=nValue
515 ){
516 if( memcmp(aField[i].zName, "private_", 8)==0 ){
517 zValue = db_conceal(zValue, nValue);
518 blob_appendf(&tktchng, "J %s %s\n", aField[i].zName, zValue);
519 }else{
520 blob_appendf(&tktchng, "J %s %#F\n", aField[i].zName, nValue, zValue);
521 }
522 nJ++;
523 }
524 }
525 }
@@ -642,18 +692,23 @@
692 sqlite3_close(db);
693 return zErr;
694 }
695 rc = sqlite3_exec(db, "SELECT tkt_id, tkt_uuid, tkt_mtime FROM ticket",
696 0, 0, 0);
 
697 if( rc!=SQLITE_OK ){
698 zErr = mprintf("schema fails to define valid a TICKET "
699 "table containing all required fields");
700 }else{
701 rc = sqlite3_exec(db, "SELECT tkt_id, tkt_mtime FROM ticketchng", 0,0,0);
702 if( rc!=SQLITE_OK ){
703 zErr = mprintf("schema fails to define valid a TICKETCHNG "
704 "table containing all required fields");
705 }
706 }
707 sqlite3_close(db);
708 }
709 return zErr;
710 }
711
712 /*
713 ** WEBPAGE: tkttimeline
714 ** URL: /tkttimeline?name=TICKETUUID&y=TYPE
@@ -998,11 +1053,11 @@
1053 int i;
1054
1055 /* read all available ticket fields */
1056 getAllTicketFields();
1057 for(i=0; i<nField; i++){
1058 printf("%s\n",aField[i].zName);
1059 }
1060 }else if( !strncmp(g.argv[3],"reports",n) ){
1061 rpt_list_reports();
1062 }else{
1063 fossil_fatal("unknown ticket list option '%s'!",g.argv[3]);
@@ -1105,21 +1160,22 @@
1160 zShort[10] = 0;
1161 if( zFile!=0 ){
1162 const char *zSrc = db_column_text(&q, 3);
1163 const char *zUser = db_column_text(&q, 5);
1164 if( zSrc==0 || zSrc[0]==0 ){
1165 fossil_print("Delete attachment %s\n", zFile);
1166 }else{
1167 fossil_print("Add attachment %s\n", zFile);
1168 }
1169 fossil_print(" by %s on %s\n", zUser, zDate);
1170 }else{
1171 pTicket = manifest_get(rid, CFTYPE_TICKET);
1172 if( pTicket ){
1173 int i;
1174
1175 fossil_print("Ticket Change by %s on %s:\n",
1176 pTicket->zUser, zDate);
1177 for(i=0; i<pTicket->nField; i++){
1178 Blob val;
1179 const char *z;
1180 z = pTicket->aField[i].zName;
1181 blob_set(&val, pTicket->aField[i].zValue);
@@ -1148,11 +1204,11 @@
1204 /* read all given ticket field/value pairs from command line */
1205 if( i==g.argc ){
1206 fossil_fatal("empty %s command aborted!",g.argv[2]);
1207 }
1208 getAllTicketFields();
1209 /* read commandline and assign fields in the aField[].zValue array */
1210 while( i<g.argc ){
1211 char *zFName;
1212 char *zFValue;
1213 int j;
1214 int append = 0;
@@ -1173,13 +1229,13 @@
1229 j = fieldId(zFName);
1230 if( j == -1 ){
1231 fossil_fatal("unknown field name '%s'!",zFName);
1232 }else{
1233 if (append) {
1234 aField[j].zAppend = zFValue;
1235 } else {
1236 aField[j].zValue = zFValue;
1237 }
1238 }
1239 }
1240
1241 /* now add the needed artifacts to the repository */
@@ -1189,25 +1245,25 @@
1245 /* append defined elements */
1246 for(i=0; i<nField; i++){
1247 char *zValue = 0;
1248 char *zPfx;
1249
1250 if (aField[i].zAppend && aField[i].zAppend[0] ){
1251 zPfx = " +";
1252 zValue = aField[i].zAppend;
1253 } else if( aField[i].zValue && aField[i].zValue[0] ){
1254 zPfx = " ";
1255 zValue = aField[i].zValue;
1256 } else {
1257 continue;
1258 }
1259 if( memcmp(aField[i].zName, "private_", 8)==0 ){
1260 zValue = db_conceal(zValue, strlen(zValue));
1261 blob_appendf(&tktchng, "J%s%s %s\n", zPfx, aField[i].zName, zValue);
1262 }else{
1263 blob_appendf(&tktchng, "J%s%s %#F\n", zPfx,
1264 aField[i].zName, strlen(zValue), zValue);
1265 }
1266 }
1267 blob_appendf(&tktchng, "K %s\n", zTktUuid);
1268 blob_appendf(&tktchng, "U %F\n", zUser);
1269 md5sum_blob(&tktchng, &cksum);
1270
+13 -2
--- src/tktsetup.c
+++ src/tktsetup.c
@@ -67,11 +67,11 @@
6767
@ CREATE TABLE ticket(
6868
@ -- Do not change any column that begins with tkt_
6969
@ tkt_id INTEGER PRIMARY KEY,
7070
@ tkt_uuid TEXT UNIQUE,
7171
@ tkt_mtime DATE,
72
-@ -- Add as many field as required below this line
72
+@ -- Add as many fields as required below this line
7373
@ type TEXT,
7474
@ status TEXT,
7575
@ subsystem TEXT,
7676
@ priority TEXT,
7777
@ severity TEXT,
@@ -79,10 +79,21 @@
7979
@ private_contact TEXT,
8080
@ resolution TEXT,
8181
@ title TEXT,
8282
@ comment TEXT
8383
@ );
84
+@ CREATE TABLE ticketchng(
85
+@ -- Do not change any column that begins with tkt_
86
+@ tkt_id INTEGER REFERENCES ticket,
87
+@ tkt_mtime DATE,
88
+@ -- Add as many fields as required below this line
89
+@ login TEXT,
90
+@ username TEXT,
91
+@ mimetype TEXT,
92
+@ icomment TEXT
93
+@ );
94
+@ CREATE INDEX ticketchng_idx1 ON ticketchng(tkt_id, tkt_mtime);
8495
;
8596
8697
/*
8798
** Return the ticket table definition
8899
*/
@@ -120,11 +131,11 @@
120131
style_header("Edit %s", zTitle);
121132
if( P("clear")!=0 ){
122133
login_verify_csrf_secret();
123134
db_unset(zDbField, 0);
124135
if( xRebuild ) xRebuild();
125
- z = zDfltValue;
136
+ cgi_redirect("tktsetup");
126137
}else if( isSubmit ){
127138
char *zErr = 0;
128139
login_verify_csrf_secret();
129140
if( xText && (zErr = xText(z))!=0 ){
130141
@ <p class="tktsetupError">ERROR: %h(zErr)</p>
131142
--- src/tktsetup.c
+++ src/tktsetup.c
@@ -67,11 +67,11 @@
67 @ CREATE TABLE ticket(
68 @ -- Do not change any column that begins with tkt_
69 @ tkt_id INTEGER PRIMARY KEY,
70 @ tkt_uuid TEXT UNIQUE,
71 @ tkt_mtime DATE,
72 @ -- Add as many field as required below this line
73 @ type TEXT,
74 @ status TEXT,
75 @ subsystem TEXT,
76 @ priority TEXT,
77 @ severity TEXT,
@@ -79,10 +79,21 @@
79 @ private_contact TEXT,
80 @ resolution TEXT,
81 @ title TEXT,
82 @ comment TEXT
83 @ );
 
 
 
 
 
 
 
 
 
 
 
84 ;
85
86 /*
87 ** Return the ticket table definition
88 */
@@ -120,11 +131,11 @@
120 style_header("Edit %s", zTitle);
121 if( P("clear")!=0 ){
122 login_verify_csrf_secret();
123 db_unset(zDbField, 0);
124 if( xRebuild ) xRebuild();
125 z = zDfltValue;
126 }else if( isSubmit ){
127 char *zErr = 0;
128 login_verify_csrf_secret();
129 if( xText && (zErr = xText(z))!=0 ){
130 @ <p class="tktsetupError">ERROR: %h(zErr)</p>
131
--- src/tktsetup.c
+++ src/tktsetup.c
@@ -67,11 +67,11 @@
67 @ CREATE TABLE ticket(
68 @ -- Do not change any column that begins with tkt_
69 @ tkt_id INTEGER PRIMARY KEY,
70 @ tkt_uuid TEXT UNIQUE,
71 @ tkt_mtime DATE,
72 @ -- Add as many fields as required below this line
73 @ type TEXT,
74 @ status TEXT,
75 @ subsystem TEXT,
76 @ priority TEXT,
77 @ severity TEXT,
@@ -79,10 +79,21 @@
79 @ private_contact TEXT,
80 @ resolution TEXT,
81 @ title TEXT,
82 @ comment TEXT
83 @ );
84 @ CREATE TABLE ticketchng(
85 @ -- Do not change any column that begins with tkt_
86 @ tkt_id INTEGER REFERENCES ticket,
87 @ tkt_mtime DATE,
88 @ -- Add as many fields as required below this line
89 @ login TEXT,
90 @ username TEXT,
91 @ mimetype TEXT,
92 @ icomment TEXT
93 @ );
94 @ CREATE INDEX ticketchng_idx1 ON ticketchng(tkt_id, tkt_mtime);
95 ;
96
97 /*
98 ** Return the ticket table definition
99 */
@@ -120,11 +131,11 @@
131 style_header("Edit %s", zTitle);
132 if( P("clear")!=0 ){
133 login_verify_csrf_secret();
134 db_unset(zDbField, 0);
135 if( xRebuild ) xRebuild();
136 cgi_redirect("tktsetup");
137 }else if( isSubmit ){
138 char *zErr = 0;
139 login_verify_csrf_secret();
140 if( xText && (zErr = xText(z))!=0 ){
141 @ <p class="tktsetupError">ERROR: %h(zErr)</p>
142

Keyboard Shortcuts

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