/* ** Copyright (c) 2010 D. Richard Hipp ** ** This program is free software; you can redistribute it and/or ** modify it under the terms of the Simplified BSD License (also ** known as the "2-Clause License" or "FreeBSD License".) ** This program is distributed in the hope that it will be useful, ** but without any warranty; without even the implied warranty of ** merchantability or fitness for a particular purpose. ** ** Author contact information: ** drh@sqlite.org ** ******************************************************************************* ** ** This file contains code used to implement the "bisect" command. ** ** This file also contains logic used to compute the closure of filename ** changes that have occurred across multiple check-ins. */ #include "config.h" #include "bisect.h" #include /* ** Local variables for this module */ static struct { int bad; /* The bad version */ int good; /* The good version */ } bisect; /* ** Find the shortest path between bad and good. */ void bisect_path(void){ PathNode *p; bisect.bad = db_lget_int("bisect-bad", 0); if( bisect.bad==0 ){ fossil_fatal("no \"bad\" version has been identified"); } bisect.good = db_lget_int("bisect-good", 0); if( bisect.good==0 ){ fossil_fatal("no \"good\" version has been identified"); } p = path_shortest(bisect.good, bisect.bad, bisect_option("direct-only")); if( p==0 ){ char *zBad = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", bisect.bad); char *zGood = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", bisect.good); fossil_fatal("no path from good ([%S]) to bad ([%S]) or back", zGood, zBad); } } /* ** The set of all bisect options. */ static const struct { const char *zName; const char *zDefault; const char *zDesc; } aBisectOption[] = { { "auto-next", "on", "Automatically run \"bisect next\" after each " "\"bisect good\" or \"bisect bad\"" }, { "direct-only", "on", "Follow only primary parent-child links, not " "merges\n" }, }; /* ** Return the value of a boolean bisect option. */ int bisect_option(const char *zName){ unsigned int i; int r = -1; for(i=0; i=0 ); return r; } /* ** COMMAND: bisect ** ** Usage: %fossil bisect SUBCOMMAND ... ** ** Run various subcommands useful for searching for bugs. ** ** fossil bisect bad ?VERSION? ** ** Identify version VERSION as non-working. If VERSION is omitted, ** the current checkout is marked as non-working. ** ** fossil bisect good ?VERSION? ** ** Identify version VERSION as working. If VERSION is omitted, ** the current checkout is marked as working. ** ** fossil bisect next ** ** Update to the next version that is halfway between the working and ** non-working versions. ** ** fossil bisect options ?NAME? ?VALUE? ** ** List all bisect options, or the value of a single option, or set the ** value of a bisect option. ** ** fossil bisect reset ** ** Reinitialize a bisect session. This cancels prior bisect history ** and allows a bisect session to start over from the beginning. ** ** fossil bisect vlist ** ** List the versions in between "bad" and "good". */ void bisect_cmd(void){ int n; const char *zCmd; db_must_be_within_tree(); if( g.argc<3 ){ usage("bisect SUBCOMMAND ARGS..."); } zCmd = g.argv[2]; n = strlen(zCmd); if( n==0 ) zCmd = "-"; if( memcmp(zCmd, "bad", n)==0 ){ int ridBad; if( g.argc==3 ){ ridBad = db_lget_int("checkout",0); }else{ ridBad = name_to_typed_rid(g.argv[3], "ci"); } db_lset_int("bisect-bad", ridBad); if( ridBad>0 && bisect_option("auto-next") && db_lget_int("bisect-good",0)>0 ){ zCmd = "next"; n = 4; }else{ return; } }else if( memcmp(zCmd, "good", n)==0 ){ int ridGood; if( g.argc==3 ){ ridGood = db_lget_int("checkout",0); }else{ ridGood = name_to_typed_rid(g.argv[3], "ci"); } db_lset_int("bisect-good", ridGood); if( ridGood>0 && bisect_option("auto-next") && db_lget_int("bisect-bad",0)>0 ){ zCmd = "next"; n = 4; }else{ return; } } if( memcmp(zCmd, "next", n)==0 ){ PathNode *pMid; bisect_path(); pMid = path_midpoint(); if( pMid==0 ){ fossil_fatal("bisect is done - there are no more intermediate versions"); } g.argv[1] = "update"; g.argv[2] = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", pMid->rid); g.argc = 3; g.fNoSync = 1; update_cmd(); }else if( memcmp(zCmd, "options", n)==0 ){ if( g.argc==3 ){ unsigned int i; for(i=0; i=sizeof(aBisectOption)/sizeof(aBisectOption[0]) ){ fossil_fatal("no such bisect option: %s", g.argv[3]); } }else{ usage("bisect option ?NAME? ?VALUE?"); } }else if( memcmp(zCmd, "reset", n)==0 ){ db_multi_exec( "DELETE FROM vvar WHERE name IN ('bisect-good', 'bisect-bad');" ); }else if( memcmp(zCmd, "vlist", n)==0 ){ PathNode *p; int vid = db_lget_int("checkout", 0); int n; Stmt s; int nStep; bisect_path(); db_prepare(&s, "SELECT substr(blob.uuid,1,20) || ' ' || " " datetime(event.mtime) FROM blob, event" " WHERE blob.rid=:rid AND event.objid=:rid" " AND event.type='ci'"); nStep = path_length(); for(p=path_last(), n=0; p; p=p->pFrom, n++){ const char *z; db_bind_int(&s, ":rid", p->rid); if( db_step(&s)==SQLITE_ROW ){ z = db_column_text(&s, 0); fossil_print("%s", z); if( p->rid==bisect.good ) fossil_print(" GOOD"); if( p->rid==bisect.bad ) fossil_print(" BAD"); if( p->rid==vid ) fossil_print(" CURRENT"); if( nStep>1 && n==nStep/2 ) fossil_print(" NEXT"); fossil_print("\n"); } db_reset(&s); } db_finalize(&s); }else{ usage("bad|good|next|reset|vlist ..."); } }