|
1
|
/* |
|
2
|
** Copyright (c) 2010 D. Richard Hipp |
|
3
|
** |
|
4
|
** This program is free software; you can redistribute it and/or |
|
5
|
** modify it under the terms of the Simplified BSD License (also |
|
6
|
** known as the "2-Clause License" or "FreeBSD License".) |
|
7
|
|
|
8
|
** This program is distributed in the hope that it will be useful, |
|
9
|
** but without any warranty; without even the implied warranty of |
|
10
|
** merchantability or fitness for a particular purpose. |
|
11
|
** |
|
12
|
** Author contact information: |
|
13
|
** [email protected] |
|
14
|
** |
|
15
|
******************************************************************************* |
|
16
|
** |
|
17
|
** This file contains code used to implement the "bisect" command. |
|
18
|
** |
|
19
|
** This file also contains logic used to compute the closure of filename |
|
20
|
** changes that have occurred across multiple check-ins. |
|
21
|
*/ |
|
22
|
#include "config.h" |
|
23
|
#include "bisect.h" |
|
24
|
#include <assert.h> |
|
25
|
|
|
26
|
/* |
|
27
|
** Local variables for this module |
|
28
|
*/ |
|
29
|
static struct { |
|
30
|
int bad; /* The bad version */ |
|
31
|
int good; /* The good version */ |
|
32
|
} bisect; |
|
33
|
|
|
34
|
/* |
|
35
|
** Find the shortest path between bad and good. |
|
36
|
*/ |
|
37
|
void bisect_path(void){ |
|
38
|
PathNode *p; |
|
39
|
bisect.bad = db_lget_int("bisect-bad", 0); |
|
40
|
bisect.good = db_lget_int("bisect-good", 0); |
|
41
|
if( bisect.good>0 && bisect.bad==0 ){ |
|
42
|
path_shortest(bisect.good, bisect.good, 0, 0, 0, 0); |
|
43
|
}else if( bisect.bad>0 && bisect.good==0 ){ |
|
44
|
path_shortest(bisect.bad, bisect.bad, 0, 0, 0, 0); |
|
45
|
}else if( bisect.bad==0 && bisect.good==0 ){ |
|
46
|
fossil_fatal("neither \"good\" nor \"bad\" versions have been identified"); |
|
47
|
}else{ |
|
48
|
Bag skip; |
|
49
|
int bDirect = bisect_option("direct-only"); |
|
50
|
char *zLog = db_lget("bisect-log",""); |
|
51
|
Blob log, id; |
|
52
|
bag_init(&skip); |
|
53
|
blob_init(&log, zLog, -1); |
|
54
|
while( blob_token(&log, &id) ){ |
|
55
|
if( blob_str(&id)[0]=='s' ){ |
|
56
|
bag_insert(&skip, atoi(blob_str(&id)+1)); |
|
57
|
} |
|
58
|
} |
|
59
|
blob_reset(&log); |
|
60
|
p = path_shortest(bisect.good, bisect.bad, bDirect, 0, &skip, 0); |
|
61
|
bag_clear(&skip); |
|
62
|
if( p==0 ){ |
|
63
|
char *zBad = db_text(0,"SELECT uuid FROM blob WHERE rid=%d",bisect.bad); |
|
64
|
char *zGood = db_text(0,"SELECT uuid FROM blob WHERE rid=%d",bisect.good); |
|
65
|
fossil_fatal("no path from good ([%S]) to bad ([%S]) or back", |
|
66
|
zGood, zBad); |
|
67
|
} |
|
68
|
} |
|
69
|
} |
|
70
|
|
|
71
|
/* |
|
72
|
** The set of all bisect options. |
|
73
|
*/ |
|
74
|
static const struct { |
|
75
|
const char *zName; |
|
76
|
const char *zDefault; |
|
77
|
const char *zDesc; |
|
78
|
} aBisectOption[] = { |
|
79
|
{ "auto-next", "on", "Automatically run \"bisect next\" after each " |
|
80
|
"\"bisect good\", \"bisect bad\", or \"bisect " |
|
81
|
"skip\"" }, |
|
82
|
{ "direct-only", "on", "Follow only primary parent-child links, not " |
|
83
|
"merges\n" }, |
|
84
|
{ "display", "chart", "Command to run after \"next\". \"chart\", " |
|
85
|
"\"log\", \"status\", or \"none\"" }, |
|
86
|
{ "linear", "off", "Do a linear scan rather than a true bisect, " |
|
87
|
"stopping at the first \"bad\" result"}, |
|
88
|
}; |
|
89
|
|
|
90
|
/* |
|
91
|
** Return the value of a boolean bisect option. |
|
92
|
*/ |
|
93
|
int bisect_option(const char *zName){ |
|
94
|
unsigned int i; |
|
95
|
int r = -1; |
|
96
|
for(i=0; i<count(aBisectOption); i++){ |
|
97
|
if( fossil_strcmp(zName, aBisectOption[i].zName)==0 ){ |
|
98
|
char *zLabel = mprintf("bisect-%s", zName); |
|
99
|
char *z; |
|
100
|
if( g.localOpen ){ |
|
101
|
z = db_lget(zLabel, (char*)aBisectOption[i].zDefault); |
|
102
|
}else{ |
|
103
|
z = (char*)aBisectOption[i].zDefault; |
|
104
|
} |
|
105
|
if( is_truth(z) ) r = 1; |
|
106
|
if( is_false(z) ) r = 0; |
|
107
|
if( r<0 ) r = is_truth(aBisectOption[i].zDefault); |
|
108
|
free(zLabel); |
|
109
|
break; |
|
110
|
} |
|
111
|
} |
|
112
|
assert( r>=0 ); |
|
113
|
return r; |
|
114
|
} |
|
115
|
|
|
116
|
/* |
|
117
|
** List a bisect path. |
|
118
|
*/ |
|
119
|
static void bisect_list(int abbreviated){ |
|
120
|
PathNode *p; |
|
121
|
int vid = db_lget_int("checkout", 0); |
|
122
|
int n; |
|
123
|
Stmt s; |
|
124
|
int nStep; |
|
125
|
int nHidden = 0; |
|
126
|
bisect_path(); |
|
127
|
db_prepare(&s, "SELECT blob.uuid, datetime(event.mtime) " |
|
128
|
" FROM blob, event" |
|
129
|
" WHERE blob.rid=:rid AND event.objid=:rid" |
|
130
|
" AND event.type='ci'"); |
|
131
|
nStep = path_length(); |
|
132
|
if( abbreviated ){ |
|
133
|
for(p=path_last(); p; p=p->pFrom) p->isHidden = 1; |
|
134
|
for(p=path_last(), n=0; p; p=p->pFrom, n++){ |
|
135
|
if( p->rid==bisect.good |
|
136
|
|| p->rid==bisect.bad |
|
137
|
|| p->rid==vid |
|
138
|
|| (nStep>1 && n==nStep/2) |
|
139
|
){ |
|
140
|
p->isHidden = 0; |
|
141
|
if( p->pFrom ) p->pFrom->isHidden = 0; |
|
142
|
} |
|
143
|
} |
|
144
|
for(p=path_last(); p; p=p->pFrom){ |
|
145
|
if( p->pFrom && p->pFrom->isHidden==0 ) p->isHidden = 0; |
|
146
|
} |
|
147
|
} |
|
148
|
for(p=path_last(), n=0; p; p=p->pFrom, n++){ |
|
149
|
if( p->isHidden && (nHidden || (p->pFrom && p->pFrom->isHidden)) ){ |
|
150
|
nHidden++; |
|
151
|
continue; |
|
152
|
}else if( nHidden ){ |
|
153
|
fossil_print(" ... %d other check-ins omitted\n", nHidden); |
|
154
|
nHidden = 0; |
|
155
|
} |
|
156
|
db_bind_int(&s, ":rid", p->rid); |
|
157
|
if( db_step(&s)==SQLITE_ROW ){ |
|
158
|
const char *zUuid = db_column_text(&s, 0); |
|
159
|
const char *zDate = db_column_text(&s, 1); |
|
160
|
fossil_print("%s %S", zDate, zUuid); |
|
161
|
if( p->rid==bisect.good ) fossil_print(" GOOD"); |
|
162
|
if( p->rid==bisect.bad ) fossil_print(" BAD"); |
|
163
|
if( p->rid==vid ) fossil_print(" CURRENT"); |
|
164
|
if( nStep>1 && n==nStep/2 ) fossil_print(" NEXT"); |
|
165
|
fossil_print("\n"); |
|
166
|
} |
|
167
|
db_reset(&s); |
|
168
|
} |
|
169
|
db_finalize(&s); |
|
170
|
} |
|
171
|
|
|
172
|
/* |
|
173
|
** Append a new entry to the bisect log. Update the bisect-good or |
|
174
|
** bisect-bad values as appropriate. |
|
175
|
** |
|
176
|
** The bisect-log consists of a list of tokens. Each token is an |
|
177
|
** integer RID of a check-in. The RID is negative for "bad" check-ins |
|
178
|
** and positive for "good" check-ins. |
|
179
|
*/ |
|
180
|
static void bisect_append_log(int rid){ |
|
181
|
if( rid<0 ){ |
|
182
|
if( db_lget_int("bisect-bad",0)==(-rid) ) return; |
|
183
|
db_lset_int("bisect-bad", -rid); |
|
184
|
}else{ |
|
185
|
if( db_lget_int("bisect-good",0)==rid ) return; |
|
186
|
db_lset_int("bisect-good", rid); |
|
187
|
} |
|
188
|
db_multi_exec( |
|
189
|
"REPLACE INTO vvar(name,value) VALUES('bisect-log'," |
|
190
|
"COALESCE((SELECT value||' ' FROM vvar WHERE name='bisect-log'),'')" |
|
191
|
" || '%d')", rid); |
|
192
|
} |
|
193
|
|
|
194
|
/* |
|
195
|
** Append a new skip entry to the bisect log. |
|
196
|
*/ |
|
197
|
static void bisect_append_skip(int rid){ |
|
198
|
db_multi_exec( |
|
199
|
"UPDATE vvar SET value=value||' s%d' WHERE name='bisect-log'", rid |
|
200
|
); |
|
201
|
} |
|
202
|
|
|
203
|
/* |
|
204
|
** Append a VALUES entry to the bilog table insert |
|
205
|
*/ |
|
206
|
static void bisect_log_append(Blob *pSql,int iSeq,const char *zStat,int iRid){ |
|
207
|
if( (iSeq%6)==3 ){ |
|
208
|
blob_append_sql(pSql, ",\n "); |
|
209
|
}else if( iSeq>1 ){ |
|
210
|
blob_append_sql(pSql, ","); |
|
211
|
} |
|
212
|
if( zStat ){ |
|
213
|
blob_append_sql(pSql, "(%d,%Q,%d)", iSeq, zStat, iRid); |
|
214
|
}else{ |
|
215
|
blob_append_sql(pSql, "(NULL,NULL,%d)", iRid); |
|
216
|
} |
|
217
|
} |
|
218
|
|
|
219
|
/* |
|
220
|
** Create a TEMP table named "bilog" that contains the complete history |
|
221
|
** of the current bisect. |
|
222
|
** |
|
223
|
** If iCurrent>0 then it is the RID of the current check-out and is included |
|
224
|
** in the history table. |
|
225
|
** |
|
226
|
** If zDesc is not NULL, then it is the bid= query parameter to /timeline |
|
227
|
** that describes a bisect. Use the information in zDesc rather than in |
|
228
|
** the bisect-log variable. |
|
229
|
** |
|
230
|
** If bDetail is true, then also include information about every node |
|
231
|
** in between the inner-most GOOD and BAD nodes. |
|
232
|
*/ |
|
233
|
int bisect_create_bilog_table(int iCurrent, const char *zDesc, int bDetail){ |
|
234
|
char *zLog; |
|
235
|
Blob log, id; |
|
236
|
int cnt = 0; |
|
237
|
int lastGood = -1; |
|
238
|
int lastBad = -1; |
|
239
|
Blob ins = BLOB_INITIALIZER; |
|
240
|
|
|
241
|
if( zDesc!=0 ){ |
|
242
|
blob_init(&log, 0, 0); |
|
243
|
while( zDesc[0]=='y' || zDesc[0]=='n' || zDesc[0]=='s' ){ |
|
244
|
int i; |
|
245
|
char c; |
|
246
|
int rid; |
|
247
|
if( blob_size(&log) ) blob_append(&log, " ", 1); |
|
248
|
if( zDesc[0]=='n' ) blob_append(&log, "-", 1); |
|
249
|
if( zDesc[0]=='s' ) blob_append(&log, "s", 1); |
|
250
|
for(i=1; ((c = zDesc[i])>='0' && c<='9') || (c>='a' && c<='f'); i++){} |
|
251
|
if( i==1 ) break; |
|
252
|
rid = db_int(0, |
|
253
|
"SELECT rid FROM blob" |
|
254
|
" WHERE uuid LIKE '%.*q%%'" |
|
255
|
" AND EXISTS(SELECT 1 FROM plink WHERE cid=rid)", |
|
256
|
i-1, zDesc+1 |
|
257
|
); |
|
258
|
if( rid==0 ) break; |
|
259
|
blob_appendf(&log, "%d", rid); |
|
260
|
zDesc += i; |
|
261
|
while( zDesc[0]=='-' ) zDesc++; |
|
262
|
} |
|
263
|
}else{ |
|
264
|
zLog = db_lget("bisect-log",""); |
|
265
|
blob_init(&log, zLog, -1); |
|
266
|
} |
|
267
|
db_multi_exec( |
|
268
|
"CREATE TEMP TABLE bilog(" |
|
269
|
" rid INTEGER PRIMARY KEY," /* Sequence of events */ |
|
270
|
" stat TEXT," /* Type of occurrence */ |
|
271
|
" seq INTEGER UNIQUE" /* Check-in number */ |
|
272
|
");" |
|
273
|
); |
|
274
|
blob_append_sql(&ins, "INSERT OR IGNORE INTO bilog(seq,stat,rid) VALUES"); |
|
275
|
while( blob_token(&log, &id) ){ |
|
276
|
int rid; |
|
277
|
cnt++; |
|
278
|
if( blob_str(&id)[0]=='s' ){ |
|
279
|
rid = atoi(blob_str(&id)+1); |
|
280
|
bisect_log_append(&ins, cnt, "SKIP", rid); |
|
281
|
}else{ |
|
282
|
rid = atoi(blob_str(&id)); |
|
283
|
if( rid>0 ){ |
|
284
|
bisect_log_append(&ins, cnt, "GOOD", rid); |
|
285
|
lastGood = rid; |
|
286
|
}else{ |
|
287
|
bisect_log_append(&ins, cnt, "BAD", -rid); |
|
288
|
lastBad = -rid; |
|
289
|
} |
|
290
|
} |
|
291
|
} |
|
292
|
if( iCurrent>0 ){ |
|
293
|
bisect_log_append(&ins, ++cnt, "CURRENT", iCurrent); |
|
294
|
} |
|
295
|
if( bDetail && lastGood>0 && lastBad>0 ){ |
|
296
|
PathNode *p; |
|
297
|
p = path_shortest(lastGood, lastBad, bisect_option("direct-only"),0, 0, 0); |
|
298
|
while( p ){ |
|
299
|
bisect_log_append(&ins, ++cnt, 0, p->rid); |
|
300
|
p = p->u.pTo; |
|
301
|
} |
|
302
|
path_reset(); |
|
303
|
} |
|
304
|
db_exec_sql(blob_sql_text(&ins)); |
|
305
|
blob_reset(&ins); |
|
306
|
return 1; |
|
307
|
} |
|
308
|
|
|
309
|
/* Return a permalink description of a bisect. Space is obtained from |
|
310
|
** fossil_malloc() and should be freed by the caller. |
|
311
|
** |
|
312
|
** A bisect description consists of characters 'y' and 'n' and lowercase |
|
313
|
** hex digits. Each term begins with 'y' or 'n' (success or failure) and |
|
314
|
** is followed by a hash prefix in lowercase hex. |
|
315
|
*/ |
|
316
|
char *bisect_permalink(void){ |
|
317
|
char *zLog = db_lget("bisect-log",""); |
|
318
|
char *zResult; |
|
319
|
Blob log; |
|
320
|
Blob link = BLOB_INITIALIZER; |
|
321
|
Blob id; |
|
322
|
blob_init(&log, zLog, -1); |
|
323
|
while( blob_token(&log, &id) ){ |
|
324
|
const char *zUuid; |
|
325
|
int rid; |
|
326
|
char cPrefix = 'y'; |
|
327
|
if( blob_str(&id)[0]=='s' ){ |
|
328
|
rid = atoi(blob_str(&id)+1); |
|
329
|
cPrefix = 's'; |
|
330
|
}else{ |
|
331
|
rid = atoi(blob_str(&id)); |
|
332
|
if( rid<0 ){ |
|
333
|
cPrefix = 'n'; |
|
334
|
rid = -rid; |
|
335
|
} |
|
336
|
} |
|
337
|
zUuid = db_text(0,"SELECT lower(uuid) FROM blob WHERE rid=%d", rid); |
|
338
|
if( blob_size(&link)>0 ) blob_append(&link, "-", 1); |
|
339
|
blob_appendf(&link, "%c%.10s", cPrefix, zUuid); |
|
340
|
} |
|
341
|
zResult = fossil_strdup(blob_str(&link)); |
|
342
|
blob_reset(&link); |
|
343
|
blob_reset(&log); |
|
344
|
blob_reset(&id); |
|
345
|
return zResult; |
|
346
|
} |
|
347
|
|
|
348
|
/* |
|
349
|
** Show a chart of bisect "good" and "bad" versions. The chart can be |
|
350
|
** sorted either chronologically by bisect time, or by check-in time. |
|
351
|
*/ |
|
352
|
static void bisect_chart(int sortByCkinTime){ |
|
353
|
Stmt q; |
|
354
|
int iCurrent = db_lget_int("checkout",0); |
|
355
|
bisect_create_bilog_table(iCurrent, 0, 0); |
|
356
|
db_prepare(&q, |
|
357
|
"SELECT bilog.seq, bilog.stat," |
|
358
|
" substr(blob.uuid,1,16), datetime(event.mtime)," |
|
359
|
" blob.rid==%d" |
|
360
|
" FROM bilog, blob, event" |
|
361
|
" WHERE blob.rid=bilog.rid AND event.objid=bilog.rid" |
|
362
|
" AND event.type='ci'" |
|
363
|
" ORDER BY %s bilog.rowid ASC", |
|
364
|
iCurrent, (sortByCkinTime ? "event.mtime DESC, " : "") |
|
365
|
); |
|
366
|
while( db_step(&q)==SQLITE_ROW ){ |
|
367
|
const char *zGoodBad = db_column_text(&q, 1); |
|
368
|
fossil_print("%3d %-7s %s %s%s\n", |
|
369
|
db_column_int(&q, 0), |
|
370
|
zGoodBad, |
|
371
|
db_column_text(&q, 3), |
|
372
|
db_column_text(&q, 2), |
|
373
|
(db_column_int(&q, 4) && zGoodBad[0]!='C') ? " CURRENT" : ""); |
|
374
|
} |
|
375
|
db_finalize(&q); |
|
376
|
} |
|
377
|
|
|
378
|
|
|
379
|
/* |
|
380
|
** Reset the bisect subsystem. |
|
381
|
*/ |
|
382
|
void bisect_reset(void){ |
|
383
|
db_multi_exec( |
|
384
|
"DELETE FROM vvar WHERE name IN " |
|
385
|
" ('bisect-good', 'bisect-bad', 'bisect-log', 'bisect-complete'," |
|
386
|
" 'bisect-linear')" |
|
387
|
); |
|
388
|
} |
|
389
|
|
|
390
|
/* |
|
391
|
** fossil bisect run [OPTIONS] COMMAND |
|
392
|
** |
|
393
|
** Invoke COMMAND (with arguments) repeatedly to perform the bisect. |
|
394
|
** |
|
395
|
** Options: |
|
396
|
** -i|--interactive Prompt user for decisions rather than |
|
397
|
** using the return code from COMMAND |
|
398
|
** --ii Like -i but also pause after showing |
|
399
|
** the status after each step. |
|
400
|
*/ |
|
401
|
static void bisect_run(void){ |
|
402
|
const char *zCmd; |
|
403
|
int isInteractive = 0; |
|
404
|
int i; |
|
405
|
if( g.argc<4 ){ |
|
406
|
fossil_fatal("Usage: fossil bisect run [OPTIONS] COMMAND\n"); |
|
407
|
} |
|
408
|
for(i=3; i<g.argc-1; i++){ |
|
409
|
const char *zArg = g.argv[i]; |
|
410
|
if( zArg[0]=='-' && zArg[1]=='-' && zArg[2]!=0 ) zArg++; |
|
411
|
if( strcmp(zArg, "-i")==0 || strcmp(zArg, "-interactive")==0 ){ |
|
412
|
isInteractive = 1; |
|
413
|
continue; |
|
414
|
} |
|
415
|
if( strcmp(zArg, "-ii")==0 ){ |
|
416
|
isInteractive = 2; |
|
417
|
continue; |
|
418
|
} |
|
419
|
fossil_fatal("unknown command-line option: \"%s\"\n", g.argv[i]); |
|
420
|
} |
|
421
|
zCmd = g.argv[i]; |
|
422
|
if( db_int(0, "SELECT count(*) FROM vvar" |
|
423
|
" WHERE name IN ('bisect-good','bisect-bad')")!=2 ){ |
|
424
|
fossil_fatal("need good/bad boundaries to use \"fossil bisect run\""); |
|
425
|
} |
|
426
|
while( db_lget_int("bisect-complete",0)==0 ){ |
|
427
|
int rc; |
|
428
|
Blob cmd; |
|
429
|
blob_init(&cmd, 0, 0); |
|
430
|
blob_append_escaped_arg(&cmd, g.nameOfExe, 1); |
|
431
|
rc = fossil_unsafe_system(zCmd); |
|
432
|
if( isInteractive ){ |
|
433
|
Blob in; |
|
434
|
fossil_print("test-command result: %d\n", rc); |
|
435
|
while(1){ |
|
436
|
int n; |
|
437
|
char *z; |
|
438
|
prompt_user("Enter (g)ood, (b)ad, (s)kip, (a)uto, (h)alt: ", &in); |
|
439
|
n = blob_size(&in); |
|
440
|
z = blob_str(&in); |
|
441
|
if( n<1 ) continue; |
|
442
|
if( sqlite3_strnicmp("good", z, n)==0 ){ |
|
443
|
rc = 0; |
|
444
|
break; |
|
445
|
} |
|
446
|
if( sqlite3_strnicmp("bad", z, n)==0 ){ |
|
447
|
rc = 1; |
|
448
|
break; |
|
449
|
} |
|
450
|
if( sqlite3_strnicmp("skip", z, n)==0 ){ |
|
451
|
rc = 125; |
|
452
|
break; |
|
453
|
} |
|
454
|
if( sqlite3_strnicmp("auto", z, n)==0 ){ |
|
455
|
isInteractive = 0; |
|
456
|
break; |
|
457
|
} |
|
458
|
if( sqlite3_strnicmp("halt", z, n)==0 ){ |
|
459
|
return; |
|
460
|
} |
|
461
|
blob_reset(&in); |
|
462
|
} |
|
463
|
} |
|
464
|
if( rc==0 ){ |
|
465
|
blob_append(&cmd, " bisect good", -1); |
|
466
|
}else if( rc==125 ){ |
|
467
|
blob_append(&cmd, " bisect skip", -1); |
|
468
|
}else{ |
|
469
|
blob_append(&cmd, " bisect bad", -1); |
|
470
|
} |
|
471
|
fossil_print("%s\n", blob_str(&cmd)); |
|
472
|
fossil_system(blob_str(&cmd)); |
|
473
|
blob_reset(&cmd); |
|
474
|
if( isInteractive>=2 && db_lget_int("bisect-complete", 0)==0 ){ |
|
475
|
int n; |
|
476
|
char *z; |
|
477
|
Blob in; |
|
478
|
int bContinue = 1; |
|
479
|
prompt_user("Run testcase again? (Y)es or No: ", &in); |
|
480
|
n = blob_size(&in); |
|
481
|
z = blob_str(&in); |
|
482
|
if( n>0 && sqlite3_strnicmp("no", z, n)==0 ) bContinue = 0; |
|
483
|
blob_reset(&in); |
|
484
|
if( !bContinue ) break; |
|
485
|
} |
|
486
|
} |
|
487
|
} |
|
488
|
|
|
489
|
/* |
|
490
|
** COMMAND: bisect |
|
491
|
** |
|
492
|
** Usage: %fossil bisect SUBCOMMAND ... |
|
493
|
** |
|
494
|
** Run various subcommands useful for searching back through the change |
|
495
|
** history for a particular check-in that causes or fixes a problem. |
|
496
|
** |
|
497
|
** > fossil bisect bad ?VERSION? |
|
498
|
** |
|
499
|
** Identify version VERSION as non-working. If VERSION is omitted, |
|
500
|
** the current check-out is marked as non-working. |
|
501
|
** |
|
502
|
** > fossil bisect good ?VERSION? |
|
503
|
** |
|
504
|
** Identify version VERSION as working. If VERSION is omitted, |
|
505
|
** the current check-out is marked as working. |
|
506
|
** |
|
507
|
** > fossil bisect log |
|
508
|
** > fossil bisect chart |
|
509
|
** |
|
510
|
** Show a log of "good", "bad", and "skip" versions. "bisect log" |
|
511
|
** shows the events in the order that they were tested. |
|
512
|
** "bisect chart" shows them in order of check-in. |
|
513
|
** |
|
514
|
** > fossil bisect next |
|
515
|
** |
|
516
|
** Update to the next version that is halfway between the working and |
|
517
|
** non-working versions. |
|
518
|
** |
|
519
|
** > fossil bisect options ?NAME? ?VALUE? |
|
520
|
** |
|
521
|
** List all bisect options, or the value of a single option, or set the |
|
522
|
** value of a bisect option. |
|
523
|
** |
|
524
|
** > fossil bisect reset |
|
525
|
** |
|
526
|
** Reinitialize a bisect session. This cancels prior bisect history |
|
527
|
** and allows a bisect session to start over from the beginning. |
|
528
|
** |
|
529
|
** > fossil bisect run [OPTIONS] COMMAND |
|
530
|
** |
|
531
|
** Invoke COMMAND repeatedly to run the bisect. The exit code for |
|
532
|
** COMMAND should be 0 for "good", 125 for "skip", and any other value |
|
533
|
** for "bad". |
|
534
|
** |
|
535
|
** Options: |
|
536
|
** -i|--interactive Prompt the user for the good/bad/skip decision |
|
537
|
** after each step, rather than using the exit |
|
538
|
** code from COMMAND |
|
539
|
** |
|
540
|
** > fossil bisect skip ?VERSION? |
|
541
|
** |
|
542
|
** Cause VERSION (or the current check-out if VERSION is omitted) to |
|
543
|
** be ignored for the purpose of the current bisect. This might |
|
544
|
** be done, for example, because VERSION does not compile correctly |
|
545
|
** or is otherwise unsuitable to participate in this bisect. |
|
546
|
** |
|
547
|
** > fossil bisect vlist|ls|status ?-a|--all? |
|
548
|
** |
|
549
|
** List the versions in between the inner-most "bad" and "good". |
|
550
|
** |
|
551
|
** > fossil bisect ui ?HOST@USER:PATH? |
|
552
|
** |
|
553
|
** Like "fossil ui" except start on a timeline that shows only the |
|
554
|
** check-ins that are part of the current bisect. If the optional |
|
555
|
** fourth term is added, then information is shown for the bisect that |
|
556
|
** occurred in the PATH directory by USER on remote machine HOST. |
|
557
|
** |
|
558
|
** > fossil bisect undo |
|
559
|
** |
|
560
|
** Undo the most recent "good", "bad", or "skip" command. |
|
561
|
*/ |
|
562
|
void bisect_cmd(void){ |
|
563
|
int n; |
|
564
|
const char *zCmd; |
|
565
|
int foundCmd = 0; |
|
566
|
db_must_be_within_tree(); |
|
567
|
if( g.argc<3 ){ |
|
568
|
goto usage; |
|
569
|
} |
|
570
|
zCmd = g.argv[2]; |
|
571
|
n = strlen(zCmd); |
|
572
|
if( n==0 ) zCmd = "-"; |
|
573
|
if( strncmp(zCmd, "bad", n)==0 ){ |
|
574
|
int ridBad; |
|
575
|
foundCmd = 1; |
|
576
|
if( g.argc==3 ){ |
|
577
|
ridBad = db_lget_int("checkout",0); |
|
578
|
}else{ |
|
579
|
ridBad = name_to_typed_rid(g.argv[3], "ci"); |
|
580
|
} |
|
581
|
if( ridBad>0 ){ |
|
582
|
bisect_append_log(-ridBad); |
|
583
|
if( bisect_option("auto-next") && db_lget_int("bisect-good",0)>0 ){ |
|
584
|
zCmd = "next"; |
|
585
|
n = 4; |
|
586
|
} |
|
587
|
} |
|
588
|
}else if( strncmp(zCmd, "good", n)==0 ){ |
|
589
|
int ridGood; |
|
590
|
foundCmd = 1; |
|
591
|
if( g.argc==3 ){ |
|
592
|
ridGood = db_lget_int("checkout",0); |
|
593
|
}else{ |
|
594
|
ridGood = name_to_typed_rid(g.argv[3], "ci"); |
|
595
|
} |
|
596
|
if( ridGood>0 ){ |
|
597
|
bisect_append_log(ridGood); |
|
598
|
if( bisect_option("auto-next") && db_lget_int("bisect-bad",0)>0 ){ |
|
599
|
zCmd = "next"; |
|
600
|
n = 4; |
|
601
|
} |
|
602
|
} |
|
603
|
}else if( strncmp(zCmd, "skip", n)==0 ){ |
|
604
|
int ridSkip; |
|
605
|
foundCmd = 1; |
|
606
|
if( g.argc==3 ){ |
|
607
|
ridSkip = db_lget_int("checkout",0); |
|
608
|
}else{ |
|
609
|
ridSkip = name_to_typed_rid(g.argv[3], "ci"); |
|
610
|
} |
|
611
|
if( ridSkip>0 ){ |
|
612
|
bisect_append_skip(ridSkip); |
|
613
|
if( bisect_option("auto-next") |
|
614
|
&& db_lget_int("bisect-bad",0)>0 |
|
615
|
&& db_lget_int("bisect-good",0)>0 |
|
616
|
){ |
|
617
|
zCmd = "next"; |
|
618
|
n = 4; |
|
619
|
} |
|
620
|
} |
|
621
|
}else if( strncmp(zCmd, "undo", n)==0 ){ |
|
622
|
char *zLog; |
|
623
|
Blob log, id; |
|
624
|
int ridBad = 0; |
|
625
|
int ridGood = 0; |
|
626
|
int cnt = 0, i; |
|
627
|
foundCmd = 1; |
|
628
|
db_begin_transaction(); |
|
629
|
zLog = db_lget("bisect-log",""); |
|
630
|
blob_init(&log, zLog, -1); |
|
631
|
while( blob_token(&log, &id) ){ cnt++; } |
|
632
|
if( cnt==0 ){ |
|
633
|
fossil_fatal("no previous bisect steps to undo"); |
|
634
|
} |
|
635
|
blob_rewind(&log); |
|
636
|
for(i=0; i<cnt-1; i++){ |
|
637
|
int rid; |
|
638
|
blob_token(&log, &id); |
|
639
|
rid = atoi(blob_str(&id)); |
|
640
|
if( rid<0 ) ridBad = -rid; |
|
641
|
else ridGood = rid; |
|
642
|
} |
|
643
|
db_multi_exec( |
|
644
|
"UPDATE vvar SET value=substr(value,1,%d) WHERE name='bisect-log'", |
|
645
|
log.iCursor-1 |
|
646
|
); |
|
647
|
db_lset_int("bisect-bad", ridBad); |
|
648
|
db_lset_int("bisect-good", ridGood); |
|
649
|
db_end_transaction(0); |
|
650
|
if( ridBad && ridGood ){ |
|
651
|
zCmd = "next"; |
|
652
|
n = 4; |
|
653
|
} |
|
654
|
} |
|
655
|
/* No else here so that the above commands can morph themselves into |
|
656
|
** a "next" command */ |
|
657
|
if( strncmp(zCmd, "next", n)==0 ){ |
|
658
|
PathNode *pMid; |
|
659
|
char *zDisplay = db_lget("bisect-display","chart"); |
|
660
|
int m = (int)strlen(zDisplay); |
|
661
|
bisect_path(); |
|
662
|
if( db_lget_boolean("bisect-linear",0) ){ |
|
663
|
pMid = path_next(); |
|
664
|
if( pMid && pMid->rid==db_lget_int("checkout",0) ) pMid = 0; |
|
665
|
}else{ |
|
666
|
pMid = path_midpoint(); |
|
667
|
} |
|
668
|
if( pMid==0 ){ |
|
669
|
fossil_print("bisect complete\n"); |
|
670
|
db_lset_int("bisect-complete",1); |
|
671
|
}else{ |
|
672
|
int nSpan = path_length_not_hidden(); |
|
673
|
int nStep = path_search_depth(); |
|
674
|
g.argv[1] = "update"; |
|
675
|
g.argv[2] = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", pMid->rid); |
|
676
|
g.argc = 3; |
|
677
|
g.fNoSync = 1; |
|
678
|
update_cmd(); |
|
679
|
fossil_print("span: %d steps-remaining: %d\n", nSpan, nStep); |
|
680
|
} |
|
681
|
|
|
682
|
if( strncmp(zDisplay,"chart",m)==0 ){ |
|
683
|
bisect_chart(1); |
|
684
|
}else if( strncmp(zDisplay, "log", m)==0 ){ |
|
685
|
bisect_chart(0); |
|
686
|
}else if( strncmp(zDisplay, "status", m)==0 ){ |
|
687
|
bisect_list(1); |
|
688
|
} |
|
689
|
}else if( strncmp(zCmd, "log", n)==0 ){ |
|
690
|
bisect_chart(0); |
|
691
|
}else if( strncmp(zCmd, "chart", n)==0 ){ |
|
692
|
bisect_chart(1); |
|
693
|
}else if( strncmp(zCmd, "run", n)==0 ){ |
|
694
|
bisect_run(); |
|
695
|
}else if( strncmp(zCmd, "options", n)==0 ){ |
|
696
|
if( g.argc==3 ){ |
|
697
|
unsigned int i; |
|
698
|
for(i=0; i<count(aBisectOption); i++){ |
|
699
|
char *z = mprintf("bisect-%s", aBisectOption[i].zName); |
|
700
|
fossil_print(" %-15s %-6s ", aBisectOption[i].zName, |
|
701
|
db_lget(z, (char*)aBisectOption[i].zDefault)); |
|
702
|
fossil_free(z); |
|
703
|
comment_print(aBisectOption[i].zDesc, 0, 27, -1, get_comment_format()); |
|
704
|
} |
|
705
|
}else if( g.argc==4 || g.argc==5 ){ |
|
706
|
unsigned int i; |
|
707
|
n = strlen(g.argv[3]); |
|
708
|
for(i=0; i<count(aBisectOption); i++){ |
|
709
|
if( strncmp(g.argv[3], aBisectOption[i].zName, n)==0 ){ |
|
710
|
char *z = mprintf("bisect-%s", aBisectOption[i].zName); |
|
711
|
if( g.argc==5 ){ |
|
712
|
db_lset(z/*works-like:"bisect-%s"*/, g.argv[4]); |
|
713
|
} |
|
714
|
fossil_print("%s\n", db_lget(z, (char*)aBisectOption[i].zDefault)); |
|
715
|
fossil_free(z); |
|
716
|
break; |
|
717
|
} |
|
718
|
} |
|
719
|
if( i>=count(aBisectOption) ){ |
|
720
|
fossil_fatal("no such bisect option: %s", g.argv[3]); |
|
721
|
} |
|
722
|
}else{ |
|
723
|
usage("options ?NAME? ?VALUE?"); |
|
724
|
} |
|
725
|
}else if( strncmp(zCmd, "reset", n)==0 ){ |
|
726
|
bisect_reset(); |
|
727
|
}else if( strcmp(zCmd, "ui")==0 ){ |
|
728
|
char *newArgv[8]; |
|
729
|
verify_all_options(); |
|
730
|
newArgv[0] = g.argv[0]; |
|
731
|
newArgv[1] = "ui"; |
|
732
|
newArgv[2] = "--page"; |
|
733
|
newArgv[3] = "timeline?bisect"; |
|
734
|
if( g.argc==4 ){ |
|
735
|
newArgv[4] = g.argv[3]; |
|
736
|
g.argc = 5; |
|
737
|
}else{ |
|
738
|
g.argc = 4; |
|
739
|
} |
|
740
|
newArgv[g.argc] = 0; |
|
741
|
g.argv = newArgv; |
|
742
|
cmd_webserver(); |
|
743
|
}else if( strncmp(zCmd, "vlist", n)==0 |
|
744
|
|| strncmp(zCmd, "ls", n)==0 |
|
745
|
|| strncmp(zCmd, "status", n)==0 |
|
746
|
){ |
|
747
|
int fAll = find_option("all", "a", 0)!=0; |
|
748
|
bisect_list(!fAll); |
|
749
|
}else if( !foundCmd ){ |
|
750
|
usage: |
|
751
|
usage("bad|good|log|chart|next|options|reset|run|skip|status|ui|undo"); |
|
752
|
} |
|
753
|
} |
|
754
|
|