/* ** 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 /* Nodes for the shortest path algorithm. */ typedef struct BisectNode BisectNode; struct BisectNode { int rid; /* ID for this node */ int fromIsParent; /* True if pFrom is the parent of rid */ BisectNode *pFrom; /* Node we came from */ union { BisectNode *pPeer; /* List of nodes of the same generation */ BisectNode *pTo; /* Next on path from beginning to end */ } u; BisectNode *pAll; /* List of all nodes */ }; /* ** Local variables for this module */ static struct { BisectNode *pCurrent; /* Current generation of nodes */ BisectNode *pAll; /* All nodes */ Bag seen; /* Nodes seen before */ int bad; /* The bad version */ int good; /* The good version */ int nStep; /* Number of steps from good to bad */ BisectNode *pStart; /* Earliest node (bad) */ BisectNode *pEnd; /* Most recent (good) */ } bisect; /* ** Create a new node */ static BisectNode *bisect_new_node(int rid, BisectNode *pFrom, int isParent){ BisectNode *p; p = fossil_malloc( sizeof(*p) ); p->rid = rid; p->fromIsParent = isParent; p->pFrom = pFrom; p->u.pPeer = bisect.pCurrent; bisect.pCurrent = p; p->pAll = bisect.pAll; bisect.pAll = p; bisect.pEnd = p; bag_insert(&bisect.seen, rid); return p; } /* ** Reset memory used by the shortest path algorithm. */ static void bisect_reset(void){ BisectNode *p; while( bisect.pAll ){ p = bisect.pAll; bisect.pAll = p->pAll; fossil_free(p); } bag_clear(&bisect.seen); bisect.pCurrent = 0; bisect.pAll = 0; bisect.pEnd = 0; bisect.nStep = 0; } /* ** Compute the shortest path from iFrom to iTo ** ** If directOnly is true, then use only the "primary" links from parent to ** child. In other words, ignore merges. */ static BisectNode *bisect_shortest_path(int iFrom, int iTo, int directOnly){ Stmt s; BisectNode *pPrev; BisectNode *p; bisect_reset(); bisect.pStart = bisect_new_node(iFrom, 0, 0); if( iTo==iFrom ) return bisect.pStart; if( directOnly ){ db_prepare(&s, "SELECT cid, 1 FROM plink WHERE pid=:pid AND isprim " "UNION ALL " "SELECT pid, 0 FROM plink WHERE cid=:pid AND isprim" ); }else{ db_prepare(&s, "SELECT cid, 1 FROM plink WHERE pid=:pid " "UNION ALL " "SELECT pid, 0 FROM plink WHERE cid=:pid" ); } while( bisect.pCurrent ){ bisect.nStep++; pPrev = bisect.pCurrent; bisect.pCurrent = 0; while( pPrev ){ db_bind_int(&s, ":pid", pPrev->rid); while( db_step(&s)==SQLITE_ROW ){ int cid = db_column_int(&s, 0); int isParent = db_column_int(&s, 1); if( bag_find(&bisect.seen, cid) ) continue; p = bisect_new_node(cid, pPrev, isParent); if( cid==iTo ){ db_finalize(&s); return p; } } db_reset(&s); pPrev = pPrev->u.pPeer; } } bisect_reset(); return 0; } /* ** Construct the path from bisect.pStart to bisect.pEnd in the u.pTo fields. */ static void bisect_reverse_path(void){ BisectNode *p; for(p=bisect.pEnd; p && p->pFrom; p = p->pFrom){ p->pFrom->u.pTo = p; } bisect.pEnd->u.pTo = 0; assert( p==bisect.pStart ); } /* ** COMMAND: test-shortest-path ** ** Usage: %fossil test-shortest-path ?--no-merge? VERSION1 VERSION2 ** ** Report the shortest path between two checkins. If the --no-merge flag ** is used, follow only direct parent-child paths and omit merge links. */ void shortest_path_test_cmd(void){ int iFrom; int iTo; BisectNode *p; int n; int directOnly; db_find_and_open_repository(0,0); directOnly = find_option("no-merge",0,0)!=0; if( g.argc!=4 ) usage("VERSION1 VERSION2"); iFrom = name_to_rid(g.argv[2]); iTo = name_to_rid(g.argv[3]); p = bisect_shortest_path(iFrom, iTo, directOnly); if( p==0 ){ fossil_fatal("no path from %s to %s", g.argv[1], g.argv[2]); } bisect_reverse_path(); for(n=1, p=bisect.pStart; p; p=p->u.pTo, n++){ char *z; z = db_text(0, "SELECT substr(uuid,1,12) || ' ' || datetime(mtime)" " FROM blob, event" " WHERE blob.rid=%d AND event.objid=%d AND event.type='ci'", p->rid, p->rid); printf("%4d: %s", n, z); fossil_free(z); if( p->u.pTo ){ printf(" is a %s of\n", p->u.pTo->fromIsParent ? "parent" : "child"); }else{ printf("\n"); } } } /* ** Find the shortest path between bad and good. */ static BisectNode *bisect_path(void){ BisectNode *p; bisect.bad = db_lget_int("bisect-bad", 0); if( bisect.bad==0 ){ bisect.bad = db_int(0, "SELECT cid FROM plink ORDER BY mtime DESC LIMIT 1"); db_lset_int("bisect-bad", bisect.bad); } bisect.good = db_lget_int("bisect-good", 0); if( bisect.good==0 ){ bisect.good = db_int(0,"SELECT pid FROM plink ORDER BY mtime LIMIT 1"); db_lset_int("bisect-good", bisect.good); } p = bisect_shortest_path(bisect.good, bisect.bad, 0); 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); } return p; } /* ** 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 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(); 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_rid(g.argv[3]); } db_lset_int("bisect-bad", ridBad); }else if( memcmp(zCmd, "good", n)==0 ){ int ridGood; if( g.argc==3 ){ ridGood = db_lget_int("checkout",0); }else{ ridGood = name_to_rid(g.argv[3]); } db_lset_int("bisect-good", ridGood); }else if( memcmp(zCmd, "next", n)==0 ){ BisectNode *p; int n; bisect_path(); if( bisect.nStep<2 ){ fossil_fatal("bisect is done - there are no more intermediate versions"); } for(p=bisect.pEnd, n=0; p && npFrom, n++){} g.argv[1] = "update"; g.argv[2] = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", p->rid); g.argc = 3; g.fNoSync = 1; update_cmd(); }else if( memcmp(zCmd, "reset", n)==0 ){ db_multi_exec( "REPLACE INTO vvar(name, value) " " SELECT 'bisect-good', pid FROM plink ORDER BY mtime LIMIT 1;" "REPLACE INTO vvar(name, value) " " SELECT 'bisect-bad', cid FROM plink ORDER BY mtime DESC LIMIT 1;" ); }else if( memcmp(zCmd, "vlist", n)==0 ){ BisectNode *p; int vid = db_lget_int("checkout", 0); int n; Stmt s; 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'"); for(p=bisect.pEnd, 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); printf("%s", z); if( p->rid==bisect.good ) printf(" GOOD"); if( p->rid==bisect.bad ) printf(" BAD"); if( p->rid==vid ) printf(" CURRENT"); if( bisect.nStep>1 && n==bisect.nStep/2 ) printf(" NEXT"); printf("\n"); } db_reset(&s); } db_finalize(&s); }else{ usage("bad|good|next|reset|vlist ..."); } } /* ** A record of a file rename operation. */ typedef struct NameChange NameChange; struct NameChange { int origName; /* Original name of file */ int curName; /* Current name of the file */ int newName; /* Name of file in next version */ NameChange *pNext; /* List of all name changes */ }; /* ** Compute all file name changes that occur going from checkin iFrom ** to checkin iTo. ** ** The number of name changes is written into *pnChng. For each name ** change, to integers are allocated for *piChng. The first is the original ** name and the second is the new name. Space to hold *piChng is obtained ** from fossil_malloc() and should be released by the caller. ** ** This routine really has nothing to do with bisection. It is located ** in this bisect.c module in order to leverage some of the bisect ** infrastructure. */ void find_filename_changes( int iFrom, int iTo, int *pnChng, int **aiChng ){ BisectNode *p; /* For looping over path from iFrom to iTo */ NameChange *pAll = 0; /* List of all name changes seen so far */ NameChange *pChng; /* For looping through the name change list */ int nChng = 0; /* Number of files whose names have changed */ int *aChng; /* Two integers per name change */ int i; /* Loop counter */ Stmt q1; /* Query of name changes */ *pnChng = 0; *aiChng = 0; bisect_reset(); p = bisect_shortest_path(iFrom, iTo, 1); if( p==0 ) return; bisect_reverse_path(); db_prepare(&q1, "SELECT pfnid, fnid FROM mlink WHERE mid=:mid AND pfnid>0" ); for(p=bisect.pStart->u.pTo; p; p=p->u.pTo){ int fnid, pfnid; db_bind_int(&q1, ":mid", p->rid); while( db_step(&q1)==SQLITE_ROW ){ if( p->fromIsParent ){ fnid = db_column_int(&q1, 1); pfnid = db_column_int(&q1, 0); }else{ fnid = db_column_int(&q1, 0); pfnid = db_column_int(&q1, 1); } for(pChng=pAll; pChng; pChng=pChng->pNext){ if( pChng->curName==pfnid ){ pChng->newName = fnid; break; } } if( pChng==0 ){ pChng = fossil_malloc( sizeof(*pChng) ); pChng->pNext = pAll; pAll = pChng; pChng->origName = pfnid; pChng->curName = pfnid; pChng->newName = fnid; nChng++; } } for(pChng=pAll; pChng; pChng=pChng->pNext) pChng->curName = pChng->newName; db_reset(&q1); } db_finalize(&q1); if( nChng ){ *pnChng = nChng; aChng = *aiChng = fossil_malloc( nChng*2*sizeof(int) ); for(pChng=pAll, i=0; pChng; pChng=pChng->pNext, i+=2){ aChng[i] = pChng->origName; aChng[i+1] = pChng->newName; } while( pAll ){ pChng = pAll; pAll = pAll->pNext; fossil_free(pChng); } } } /* ** COMMAND: test-name-changes ** ** Usage: %fossil test-name-changes VERSION1 VERSION2 ** ** Show all filename changes that occur going from VERSION1 to VERSION2 */ void test_name_change(void){ int iFrom; int iTo; int *aChng; int nChng; int i; db_find_and_open_repository(0,0); if( g.argc!=4 ) usage("VERSION1 VERSION2"); iFrom = name_to_rid(g.argv[2]); iTo = name_to_rid(g.argv[3]); find_filename_changes(iFrom, iTo, &nChng, &aChng); for(i=0; i [%s]\n", zFrom, zTo); fossil_free(zFrom); fossil_free(zTo); } fossil_free(aChng); }