Fossil SCM

Add the "fossil bisect run" command.

drh 2021-06-21 13:53 trunk
Commit 7d4cf0ed6989c410f0d6bb8f40a6d8d50c3093f21a395a6346f6544ea775e3c0
2 files changed +96 -2 +25 -4
+96 -2
--- src/bisect.c
+++ src/bisect.c
@@ -373,13 +373,94 @@
373373
** Reset the bisect subsystem.
374374
*/
375375
void bisect_reset(void){
376376
db_multi_exec(
377377
"DELETE FROM vvar WHERE name IN "
378
- " ('bisect-good', 'bisect-bad', 'bisect-log')"
378
+ " ('bisect-good', 'bisect-bad', 'bisect-log', 'bisect-complete')"
379379
);
380380
}
381
+
382
+/*
383
+** fossil bisect run [OPTIONS] COMMAND
384
+**
385
+** Invoke COMMAND (with arguments) repeatedly to perform the bisect.
386
+** Options:
387
+**
388
+** -i|--interactive Prompt user for decisions rather than
389
+** using the return code from COMMAND
390
+*/
391
+static void bisect_run(void){
392
+ const char *zCmd;
393
+ int isInteractive = 0;
394
+ int i;
395
+ if( g.argc<4 ){
396
+ fossil_fatal("Usage: fossil bisect run [OPTIONS] COMMAND\n");
397
+ }
398
+ for(i=3; i<g.argc-1; i++){
399
+ const char *zArg = g.argv[i];
400
+ if( zArg[0]=='-' && zArg[1]=='-' && zArg[2]!=0 ) zArg++;
401
+ if( strcmp(zArg, "-i")==0 || strcmp(zArg, "-interactive")==0 ){
402
+ isInteractive = 1;
403
+ continue;
404
+ }
405
+ fossil_fatal("unknown command-line option: \"%s\"\n", g.argv[i]);
406
+ }
407
+ zCmd = g.argv[i];
408
+ if( db_int(0, "SELECT count(*) FROM vvar"
409
+ " WHERE name IN ('bisect-good','bisect-bad')")!=2 ){
410
+ fossil_fatal("need good/bad boundaries to use \"fossil bisect run\"");
411
+ }
412
+ while( db_lget_int("bisect-complete",0)==0 ){
413
+ int rc;
414
+ Blob cmd;
415
+ blob_init(&cmd, 0, 0);
416
+ blob_append_escaped_arg(&cmd, g.nameOfExe);
417
+ rc = fossil_unsafe_system(zCmd);
418
+ if( isInteractive ){
419
+ Blob in;
420
+ fossil_print("test-command result: %d\n", rc);
421
+ while(1){
422
+ int n;
423
+ char *z;
424
+ prompt_user("Enter (g)ood, (b)ad, (s)kip, (a)uto, (h)alt: ", &in);
425
+ n = blob_size(&in);
426
+ z = blob_str(&in);
427
+ if( n<1 ) continue;
428
+ if( sqlite3_strnicmp("good", z, n)==0 ){
429
+ rc = 0;
430
+ break;
431
+ }
432
+ if( sqlite3_strnicmp("bad", z, n)==0 ){
433
+ rc = 1;
434
+ break;
435
+ }
436
+ if( sqlite3_strnicmp("skip", z, n)==0 ){
437
+ rc = 125;
438
+ break;
439
+ }
440
+ if( sqlite3_strnicmp("auto", z, n)==0 ){
441
+ isInteractive = 0;
442
+ break;
443
+ }
444
+ if( sqlite3_strnicmp("halt", z, n)==0 ){
445
+ return;
446
+ }
447
+ blob_reset(&in);
448
+ }
449
+ }
450
+ if( rc==0 ){
451
+ blob_append(&cmd, " bisect good", -1);
452
+ }else if( rc==125 ){
453
+ blob_append(&cmd, " bisect skip", -1);
454
+ }else{
455
+ blob_append(&cmd, " bisect bad", -1);
456
+ }
457
+ fossil_print("%s\n", blob_str(&cmd));
458
+ fossil_system(blob_str(&cmd));
459
+ blob_reset(&cmd);
460
+ }
461
+}
381462
382463
/*
383464
** COMMAND: bisect
384465
**
385466
** Usage: %fossil bisect SUBCOMMAND ...
@@ -417,10 +498,20 @@
417498
** > fossil bisect reset
418499
**
419500
** Reinitialize a bisect session. This cancels prior bisect history
420501
** and allows a bisect session to start over from the beginning.
421502
**
503
+** > fossil bisect run [OPTIONS] COMMAND
504
+**
505
+** Invoke COMMAND repeatedly to run the bisect. The exit code for
506
+** COMMAND should be 0 for "good", 125 for "skip", and any other value
507
+** for "bad". Options:
508
+**
509
+** -i|--interactive Prompt the user for the good/bad/skip decision
510
+** after each step, rather than using the exit
511
+** code from COMMAND
512
+**
422513
** > fossil bisect skip ?VERSION?
423514
**
424515
** Cause VERSION (or the current checkout if VERSION is omitted) to
425516
** be ignored for the purpose of the current bisect. This might
426517
** be done, for example, because VERSION does not compile correctly
@@ -540,10 +631,11 @@
540631
int m = (int)strlen(zDisplay);
541632
bisect_path();
542633
pMid = path_midpoint();
543634
if( pMid==0 ){
544635
fossil_print("bisect complete\n");
636
+ db_lset_int("bisect-complete",1);
545637
}else{
546638
int nSpan = path_length_not_hidden();
547639
int nStep = path_search_depth();
548640
g.argv[1] = "update";
549641
g.argv[2] = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", pMid->rid);
@@ -562,10 +654,12 @@
562654
}
563655
}else if( strncmp(zCmd, "log", n)==0 ){
564656
bisect_chart(0);
565657
}else if( strncmp(zCmd, "chart", n)==0 ){
566658
bisect_chart(1);
659
+ }else if( strncmp(zCmd, "run", n)==0 ){
660
+ bisect_run();
567661
}else if( strncmp(zCmd, "options", n)==0 ){
568662
if( g.argc==3 ){
569663
unsigned int i;
570664
for(i=0; i<count(aBisectOption); i++){
571665
char *z = mprintf("bisect-%s", aBisectOption[i].zName);
@@ -612,8 +706,8 @@
612706
){
613707
int fAll = find_option("all", "a", 0)!=0;
614708
bisect_list(!fAll);
615709
}else if( !foundCmd ){
616710
usage:
617
- usage("bad|good|log|chart|next|options|reset|skip|status|ui|undo");
711
+ usage("bad|good|log|chart|next|options|reset|run|skip|status|ui|undo");
618712
}
619713
}
620714
--- src/bisect.c
+++ src/bisect.c
@@ -373,13 +373,94 @@
373 ** Reset the bisect subsystem.
374 */
375 void bisect_reset(void){
376 db_multi_exec(
377 "DELETE FROM vvar WHERE name IN "
378 " ('bisect-good', 'bisect-bad', 'bisect-log')"
379 );
380 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
381
382 /*
383 ** COMMAND: bisect
384 **
385 ** Usage: %fossil bisect SUBCOMMAND ...
@@ -417,10 +498,20 @@
417 ** > fossil bisect reset
418 **
419 ** Reinitialize a bisect session. This cancels prior bisect history
420 ** and allows a bisect session to start over from the beginning.
421 **
 
 
 
 
 
 
 
 
 
 
422 ** > fossil bisect skip ?VERSION?
423 **
424 ** Cause VERSION (or the current checkout if VERSION is omitted) to
425 ** be ignored for the purpose of the current bisect. This might
426 ** be done, for example, because VERSION does not compile correctly
@@ -540,10 +631,11 @@
540 int m = (int)strlen(zDisplay);
541 bisect_path();
542 pMid = path_midpoint();
543 if( pMid==0 ){
544 fossil_print("bisect complete\n");
 
545 }else{
546 int nSpan = path_length_not_hidden();
547 int nStep = path_search_depth();
548 g.argv[1] = "update";
549 g.argv[2] = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", pMid->rid);
@@ -562,10 +654,12 @@
562 }
563 }else if( strncmp(zCmd, "log", n)==0 ){
564 bisect_chart(0);
565 }else if( strncmp(zCmd, "chart", n)==0 ){
566 bisect_chart(1);
 
 
567 }else if( strncmp(zCmd, "options", n)==0 ){
568 if( g.argc==3 ){
569 unsigned int i;
570 for(i=0; i<count(aBisectOption); i++){
571 char *z = mprintf("bisect-%s", aBisectOption[i].zName);
@@ -612,8 +706,8 @@
612 ){
613 int fAll = find_option("all", "a", 0)!=0;
614 bisect_list(!fAll);
615 }else if( !foundCmd ){
616 usage:
617 usage("bad|good|log|chart|next|options|reset|skip|status|ui|undo");
618 }
619 }
620
--- src/bisect.c
+++ src/bisect.c
@@ -373,13 +373,94 @@
373 ** Reset the bisect subsystem.
374 */
375 void bisect_reset(void){
376 db_multi_exec(
377 "DELETE FROM vvar WHERE name IN "
378 " ('bisect-good', 'bisect-bad', 'bisect-log', 'bisect-complete')"
379 );
380 }
381
382 /*
383 ** fossil bisect run [OPTIONS] COMMAND
384 **
385 ** Invoke COMMAND (with arguments) repeatedly to perform the bisect.
386 ** Options:
387 **
388 ** -i|--interactive Prompt user for decisions rather than
389 ** using the return code from COMMAND
390 */
391 static void bisect_run(void){
392 const char *zCmd;
393 int isInteractive = 0;
394 int i;
395 if( g.argc<4 ){
396 fossil_fatal("Usage: fossil bisect run [OPTIONS] COMMAND\n");
397 }
398 for(i=3; i<g.argc-1; i++){
399 const char *zArg = g.argv[i];
400 if( zArg[0]=='-' && zArg[1]=='-' && zArg[2]!=0 ) zArg++;
401 if( strcmp(zArg, "-i")==0 || strcmp(zArg, "-interactive")==0 ){
402 isInteractive = 1;
403 continue;
404 }
405 fossil_fatal("unknown command-line option: \"%s\"\n", g.argv[i]);
406 }
407 zCmd = g.argv[i];
408 if( db_int(0, "SELECT count(*) FROM vvar"
409 " WHERE name IN ('bisect-good','bisect-bad')")!=2 ){
410 fossil_fatal("need good/bad boundaries to use \"fossil bisect run\"");
411 }
412 while( db_lget_int("bisect-complete",0)==0 ){
413 int rc;
414 Blob cmd;
415 blob_init(&cmd, 0, 0);
416 blob_append_escaped_arg(&cmd, g.nameOfExe);
417 rc = fossil_unsafe_system(zCmd);
418 if( isInteractive ){
419 Blob in;
420 fossil_print("test-command result: %d\n", rc);
421 while(1){
422 int n;
423 char *z;
424 prompt_user("Enter (g)ood, (b)ad, (s)kip, (a)uto, (h)alt: ", &in);
425 n = blob_size(&in);
426 z = blob_str(&in);
427 if( n<1 ) continue;
428 if( sqlite3_strnicmp("good", z, n)==0 ){
429 rc = 0;
430 break;
431 }
432 if( sqlite3_strnicmp("bad", z, n)==0 ){
433 rc = 1;
434 break;
435 }
436 if( sqlite3_strnicmp("skip", z, n)==0 ){
437 rc = 125;
438 break;
439 }
440 if( sqlite3_strnicmp("auto", z, n)==0 ){
441 isInteractive = 0;
442 break;
443 }
444 if( sqlite3_strnicmp("halt", z, n)==0 ){
445 return;
446 }
447 blob_reset(&in);
448 }
449 }
450 if( rc==0 ){
451 blob_append(&cmd, " bisect good", -1);
452 }else if( rc==125 ){
453 blob_append(&cmd, " bisect skip", -1);
454 }else{
455 blob_append(&cmd, " bisect bad", -1);
456 }
457 fossil_print("%s\n", blob_str(&cmd));
458 fossil_system(blob_str(&cmd));
459 blob_reset(&cmd);
460 }
461 }
462
463 /*
464 ** COMMAND: bisect
465 **
466 ** Usage: %fossil bisect SUBCOMMAND ...
@@ -417,10 +498,20 @@
498 ** > fossil bisect reset
499 **
500 ** Reinitialize a bisect session. This cancels prior bisect history
501 ** and allows a bisect session to start over from the beginning.
502 **
503 ** > fossil bisect run [OPTIONS] COMMAND
504 **
505 ** Invoke COMMAND repeatedly to run the bisect. The exit code for
506 ** COMMAND should be 0 for "good", 125 for "skip", and any other value
507 ** for "bad". Options:
508 **
509 ** -i|--interactive Prompt the user for the good/bad/skip decision
510 ** after each step, rather than using the exit
511 ** code from COMMAND
512 **
513 ** > fossil bisect skip ?VERSION?
514 **
515 ** Cause VERSION (or the current checkout if VERSION is omitted) to
516 ** be ignored for the purpose of the current bisect. This might
517 ** be done, for example, because VERSION does not compile correctly
@@ -540,10 +631,11 @@
631 int m = (int)strlen(zDisplay);
632 bisect_path();
633 pMid = path_midpoint();
634 if( pMid==0 ){
635 fossil_print("bisect complete\n");
636 db_lset_int("bisect-complete",1);
637 }else{
638 int nSpan = path_length_not_hidden();
639 int nStep = path_search_depth();
640 g.argv[1] = "update";
641 g.argv[2] = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", pMid->rid);
@@ -562,10 +654,12 @@
654 }
655 }else if( strncmp(zCmd, "log", n)==0 ){
656 bisect_chart(0);
657 }else if( strncmp(zCmd, "chart", n)==0 ){
658 bisect_chart(1);
659 }else if( strncmp(zCmd, "run", n)==0 ){
660 bisect_run();
661 }else if( strncmp(zCmd, "options", n)==0 ){
662 if( g.argc==3 ){
663 unsigned int i;
664 for(i=0; i<count(aBisectOption); i++){
665 char *z = mprintf("bisect-%s", aBisectOption[i].zName);
@@ -612,8 +706,8 @@
706 ){
707 int fAll = find_option("all", "a", 0)!=0;
708 bisect_list(!fAll);
709 }else if( !foundCmd ){
710 usage:
711 usage("bad|good|log|chart|next|options|reset|run|skip|status|ui|undo");
712 }
713 }
714
+25 -4
--- src/util.c
+++ src/util.c
@@ -164,13 +164,21 @@
164164
}
165165
return zStart;
166166
}
167167
168168
/*
169
-** If this local variable is set, fossil_assert_safe_command_string()
170
-** returns false on an unsafe command-string rather than abort. Set
171
-** this variable for testing.
169
+** This local variable determines the behavior of
170
+** fossil_assert_safe_command_string():
171
+**
172
+** 0 (default) fossil_panic() on an unsafe command string
173
+**
174
+** 1 Print an error but continue process. Used for
175
+** testing of fossil_assert_safe_command_string().
176
+**
177
+** 2 No-op. Used to allow any arbitrary command string
178
+** through fossil_system(), such as when invoking
179
+** COMMAND in "fossil bisect run COMMAND".
172180
*/
173181
static int safeCmdStrTest = 0;
174182
175183
/*
176184
** Check the input string to ensure that it is safe to pass into system().
@@ -266,15 +274,16 @@
266274
}
267275
}
268276
}
269277
if( inQuote ) unsafe = i;
270278
#endif
271
- if( unsafe ){
279
+ if( unsafe && safeCmdStrTest<2 ){
272280
char *zMsg = mprintf("Unsafe command string: %s\n%*shere ----^",
273281
z, unsafe+13, "");
274282
if( safeCmdStrTest ){
275283
fossil_print("%z\n", zMsg);
284
+ fossil_free(zMsg);
276285
}else{
277286
fossil_panic("%s", zMsg);
278287
}
279288
}
280289
}
@@ -315,10 +324,22 @@
315324
rc = system(zOrigCmd);
316325
fossil_limit_memory(1);
317326
#endif
318327
return rc;
319328
}
329
+
330
+/*
331
+** Like "fossil_system()" but does not check the command-string for
332
+** potential security problems.
333
+*/
334
+int fossil_unsafe_system(const char *zOrigCmd){
335
+ int rc;
336
+ safeCmdStrTest = 2;
337
+ rc = fossil_system(zOrigCmd);
338
+ safeCmdStrTest = 0;
339
+ return rc;
340
+}
320341
321342
/*
322343
** COMMAND: test-fossil-system
323344
**
324345
** Read lines of input and send them to fossil_system() for evaluation.
325346
--- src/util.c
+++ src/util.c
@@ -164,13 +164,21 @@
164 }
165 return zStart;
166 }
167
168 /*
169 ** If this local variable is set, fossil_assert_safe_command_string()
170 ** returns false on an unsafe command-string rather than abort. Set
171 ** this variable for testing.
 
 
 
 
 
 
 
 
172 */
173 static int safeCmdStrTest = 0;
174
175 /*
176 ** Check the input string to ensure that it is safe to pass into system().
@@ -266,15 +274,16 @@
266 }
267 }
268 }
269 if( inQuote ) unsafe = i;
270 #endif
271 if( unsafe ){
272 char *zMsg = mprintf("Unsafe command string: %s\n%*shere ----^",
273 z, unsafe+13, "");
274 if( safeCmdStrTest ){
275 fossil_print("%z\n", zMsg);
 
276 }else{
277 fossil_panic("%s", zMsg);
278 }
279 }
280 }
@@ -315,10 +324,22 @@
315 rc = system(zOrigCmd);
316 fossil_limit_memory(1);
317 #endif
318 return rc;
319 }
 
 
 
 
 
 
 
 
 
 
 
 
320
321 /*
322 ** COMMAND: test-fossil-system
323 **
324 ** Read lines of input and send them to fossil_system() for evaluation.
325
--- src/util.c
+++ src/util.c
@@ -164,13 +164,21 @@
164 }
165 return zStart;
166 }
167
168 /*
169 ** This local variable determines the behavior of
170 ** fossil_assert_safe_command_string():
171 **
172 ** 0 (default) fossil_panic() on an unsafe command string
173 **
174 ** 1 Print an error but continue process. Used for
175 ** testing of fossil_assert_safe_command_string().
176 **
177 ** 2 No-op. Used to allow any arbitrary command string
178 ** through fossil_system(), such as when invoking
179 ** COMMAND in "fossil bisect run COMMAND".
180 */
181 static int safeCmdStrTest = 0;
182
183 /*
184 ** Check the input string to ensure that it is safe to pass into system().
@@ -266,15 +274,16 @@
274 }
275 }
276 }
277 if( inQuote ) unsafe = i;
278 #endif
279 if( unsafe && safeCmdStrTest<2 ){
280 char *zMsg = mprintf("Unsafe command string: %s\n%*shere ----^",
281 z, unsafe+13, "");
282 if( safeCmdStrTest ){
283 fossil_print("%z\n", zMsg);
284 fossil_free(zMsg);
285 }else{
286 fossil_panic("%s", zMsg);
287 }
288 }
289 }
@@ -315,10 +324,22 @@
324 rc = system(zOrigCmd);
325 fossil_limit_memory(1);
326 #endif
327 return rc;
328 }
329
330 /*
331 ** Like "fossil_system()" but does not check the command-string for
332 ** potential security problems.
333 */
334 int fossil_unsafe_system(const char *zOrigCmd){
335 int rc;
336 safeCmdStrTest = 2;
337 rc = fossil_system(zOrigCmd);
338 safeCmdStrTest = 0;
339 return rc;
340 }
341
342 /*
343 ** COMMAND: test-fossil-system
344 **
345 ** Read lines of input and send them to fossil_system() for evaluation.
346

Keyboard Shortcuts

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